SpringBoot


Author
|
Earl
Describe
|
该文档介绍SpringBoot2.0的特性和使用方法
Git
|
https://github.com/Earl-Li/springboot-demo.git
Last Update
|
2024-01-14

 

搭建SpringBoot应用

需求:浏览发送/hello请求,服务器响应Hello,SpringBoot2

使用原生spring的弊端:使用原生spring的方式,需要导入spring和springMVC依赖,编写配置文件,开发代码,将tomcat引入idea,将应用部署在tomcat上启动运行,依赖管理麻烦,配置文件麻烦

maven配置

注意:需要在maven的settings.xml中profiles标签配置jdk的版本为8,避免编译过程使用其他版本

web应用搭建

使用SpringBoot搭建一个简单的web应用实例

配置pom.xml

  1. 在pom.xml中引入父工程spring-boot-starter-parent

  2. 在pom.xml中引入web的场景启动器依赖spring-boot-starter-web

主程序类

  1. 编写主程序类如MainApplication

    • 必须用@SpringBootApplication注解告诉springBoot这是一个springboot应用,也称主程序类或主配置类

    • 在主程序类的主方法中编写代码SpringApplication.run(MainApplication.class,args)传入主程序类的class对象和主方法的args,该方法的作用相当于让主程序类对应的springboot应用跑起来

    • 可以直接运行主方法,也可以直接点debug运行SpringBoot应用

业务代码

  1. 在主程序类所在的包下创建控制器,编写对应/hello请求路径的对应控制器方法,并响应浏览器一段字符串,注意这里的/hello就是webapp的路径,即http://localhost:8080/hello

测试

  1. 直接运行主程序的主方法即可(对比以前还需要整Tomcat和很多配置文件)

  2. 也可以直接点击debug按钮或者运行符号

    经过测试,浏览器访问确实Ok

简化配置

springBoot最强大的功能是简化配置(比如改tomcat端口号,以前需要打开tomcat的配置文件改端口号)

  1. springboot可以直接在类路径下的一个属性配置文件中修改所有的配置信息,该文件有固定名字application.properties

  2. springboot本身有默认配置,在application.properties文件中可以进行修改的配置可以参考官方文档的Application Properties

    比如服务器的端口名固定为server.port;除此外还有配置的默认值信息

    使用ctrl+f能在文档中进行搜索,IDEA对属性名还有提示功能

简化部署

  1. 使用springboot的spring-boot-maven-plugin插件把springboot应用打成一个可执行的jar包

    这个jar包称为小胖jar(fat.jars),包含整个运行环境,可以直接通过DOS窗口的当前目录使用命令:

    java -jar boot-01-helloworld-1.0-SNAPSHOT.jar直接运行,经验证OK

     

    实际生产环境部署也是直接引入打包插件打成小胖jar,直接在目标服务器执行即可,注意:要关闭DOS命 令窗口的快速编辑模式,否则鼠标只要一点DOS命令窗口,springboot应用的启动进程就会卡住,以前 需要打成war包部署到服务器上

     

    小胖jar中BOOT-INF下的lib下是第三方的所有jar包,BOOT-INF下的classes下是我们自己写的代码和配置 文件

  2. pom.xml插件配置代码

 

其他事项


  1. DOS窗口检查java和mvn版本的命令

    • java -version

    • mvn -v

 

自动配置原理

SpringBoot两大优秀特性 : 依赖管理|自动配置

依赖管理

父工程做依赖管理

  1. 在pom.xml使用父工程spring-boot-starter-parent进行依赖版本号管理

  2. 父工程的pom.xml中还有父工程spring-boot-dependencies

    • 在spring-boot-dependencies的pom.xml中的properties标签中声明了几乎开发中使用的所有jar包的版本

    • 使用ctrl+f可以搜索响应的依赖信息

    • 需要使用未被starter启动器引入的依赖,只需引入相关的groupId和artifactId,无需添加版本信息,默认使用父工程中设置的默认版本,注意引入非版本仲裁的jar要写版本号。

    • 如果想自己指定依赖的版本,直接在pom.xml中新建properties标签,依照spring-boot-dependencies中的依赖版本属性名格式配置自己想要的版本,springboot会自动根据就近原则选取用户自己配置的版本

  1. 导入starter场景启动器

    官方文档的starter相关信息位置:Using Spring Boot下的starters

    • starter是一组依赖集合的描述,只要引入一个starter,这个starter开发场景的所有依赖都被引入了

      原理实际上是依赖的传递性,因为starter-*依赖依赖于开发某个场景所需要的全部依赖

    • Spring官方starter的命名规范:spring-boot-starter-**为某种场景

    • 第三方提供的starter的命名规范:*-spring-boot-starter

    • 所有starter的最基本依赖都是spring-boot-starter依赖

 

自动配置

在spring-boot-starter-web的pom.xml下配置了Tomcat、SpringMVC等依赖

  1. 自动配置Tomcat

    • 自动引入Tomcat依赖

    • 自动配置Tomcat,关于如何配置和启动Tomcat后面再讲

  2. 自动配置SpringMVC

    • 自动引入SpringMVC全套组件

    • 自动配好了SpringMVC的常用组件(功能)

      • DispatcherServlet

      • characterEncoding?是视图解析器的属性吗,作为属性为什么可以作为IoC容器的组件?

      • viewResolver 视图解析器

      • mutipartResolver 文件上传解析器

      • ...SpringBoot已经帮用户配置好了所有的web开发常见场景,会自动在容器中生效,生效原理以后再说

        以上组件都可以获取IoC容器并获取组件名字打印查看

  3. springboot中默认的包扫描规则

    • 官方文档的Using Spring Boot下的Structuring Your Code下有默认包扫描规则

    • 默认规则为主程序所在的包及该包下的所有子包都能被扫描到,无需再配置包扫描

    • 如果必须要扫描主程序类所在包外的包或类,可以在标注主程序类的@SpringBootApplication注解中为其属性scanBasePackages赋值更大的扫描范围即可

    • 可以直接把@SpringBootApplication注解拆成三个注解,并在注解之一@ComponentScan中扩大包扫描的范围,这样不会报注解重复异常

  4. 自动配置的默认值

    • SpringBoot的自动配置都有默认值,如果想要修改直接在application.properties文件中修改对应的key和value即可

    • 默认配置最终都是映射到某个类上,如:MultipartProperties

      配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

      研究如何映射绑定和使用后面介绍

  5. 自动配置按需加载

    • 引入了哪些场景启动器,对应的自动配置才会开启

    • SpringBoot所有的自动配置功能都在spring-boot-starter包依赖的spring-boot-autoconfigure 包里面,在外部libirary中的autoconfigure中可以找到,里面按照amqp、aop、拼接码、缓存、批处理等等场景按包进行了分类,这些自动配置类中发红的对象就是没有生效的,比如批处理,导入批处理场景启动器spring-boot-starter-batch部分报红的对象就会恢复正常

 

底层注解

@Configuration

@Configuration配置类

原生Spring向IoC容器中添加bean对象的方式:

  • 在xml文件中用bean标签向IoC容器添加bean对象(组件)

  • 在类上标注@Component、@Controller、@Service、@Repository注解代表该类是一个组件

  1. 使用@Configuration(意为配置)标注类可以告诉SpringBoot这个类是一个配置类,可以取代Spring配置文件的作用,配置文件的功能这个类都有,配置类本身也是IoC容器的组件

  2. 在配置类中使用@Bean注解标注组件注册方法,方法返回的对象会被作为组件自动纳入IoC容器的管理

    以方法名作为组件的id,返回的类型就是组件类型,返回的值就是组件在容器中的实例

    添加@Bean注解的value属性可以手动设置组件的id

    默认被注册组件是单实例的

  3. @Configuration注解的proxyBeanMethods属性

    • proxyBeanMethods属性为true时(即Full模式,全配置):

      此时配置类在容器中是被CGLIB增强的代理对象,使用代理对象调用组件注册方法,SpringBoot总会检查该组件是否已在容器中存在如果有会自动保持组件单实例

      实现原理是容器中有自动去容器中找组件

    • proxyBeanMethods属性为false时(即Lite模式,轻量级配置):

      此时配置类在容器中是普通对象,调用组件注册方法时,SpringBoot不会保持目标组件的单实例

  4. proxyBeanMethods属性可以用来方便的处理组件依赖的场景

    如User中有Pet属性,user想获取容器中已经注册的Pet组件,直接在Full模式下调用配置类的pet组件注册方法即可,注意Lite模式下这种方式为user获取的pet属性不是已经在容器中注册的组件

    • proxyBeanMethods属性的最佳实践

     

@Import

@Import导入组件

@Import注解的作用也是给IoC容器中导入一个组件

  1. @Import注解需要标注在配置类或者组件类的类名上

    • 该注解的value属性是一个class数组,可以导入用户或者第三方的类的class对象

    • 作用是将指定类型的组件导入IoC容器,调用对应类型的无参构造创建出对应的组件对象

    • 通过@Import注解导入的组件的默认名字是对应类型的全限定类名

    ~@Import注解使用实例

    ~@Import注解的测试代码

  2. @Import注解还有高级用法,参考:08尚硅谷组件注册-@Import-给容器中快速导入一个组件

@Conditional

@Conditional条件装配

@Conditional条件装配注解的作用是:满足Conditional指定的条件,则对标注组件进行组件注入

  1. @Conditional是一个根注解,其下派生出非常多的派生注解

    • 派生注解可以标注在组件注册方法上,表示条件装配只对该方法对应的组件生效

    • 派生注解也可以标注在配置类或者组件类上,表示条件装配对配置类下的所有组件均有效或对组件类有效

@Conditional是一个根注解,其下派生出非常多的派生注解

  1. @Conditional的常用派生注解

    • @ConditionalOnBean

      当容器中存在用户指定的组件时才向容器注入指定的组件

    • @ConditionalOnMissingBean

      当容器中没有用户指定的组件时才向容器注入指定的组件,没有指定就表示容器中没有当前组件就配置当前组件,配置了当前组件就不配置了

    • @ConditionalOnClass

      当容器中存在用户指定的类时才向容器注入指定的组件

    • @ConditionalOnMissingClass

      当容器中没有用户指定的类时才向容器注入指定的组件

    • @ConditionalOnResource

      当类路径下存在用户指定的资源时才向容器注入指定的组件

    • @ConditionalOnJava

      当Java是用户指定的版本号时才向容器注入指定的组件

    • @ConditionalOnWebApplication

      当应用是一个web应用时才向容器注入指定的组件

    • @ConditionalOnWebApplication

      当应用不是一个web应用时才向容器注入指定的组件

    • @ConditionalOnSingleCandidate

      当特定组件只有一个实例或者多个实例中有一个主实例时才向容器注入指定的组件

    • @ConditionalOnProperty

      当配置文件中配置了特定属性时才向容器注入指定的组件

  2. 以@ConditionalOnBean举例说明@Conditional派生注解的用法

    ~@ConditionalOnBean和@ConditionalOnMissingBean代码实例:

    ~测试代码:

 

@ImportResource

@ImportResource导入Spring配置文件

有些公司还在使用spring.xml配置IoC组件,这些xml文件需要使用BeanFactory applicationContext=new ClassPathXmlApplicationContext("spring.xml");获取Bean工厂才能创建对应的IoC容器并生成对应的组件,无法直接生效在springboot的IoC容器中生成对应的组件,使用SpringBoot需要对应去添加配置类和@Bean注解来生成组件,很麻烦

  1. SpringBoot提供@ImportResource注解配置在随意配置类上导入Spring配置文件,使配置文件中的组件在springboot的IoC容器中生效,无需添加配置类和@Bean注解

    ~beans.xml配置代码

    ~@ImportResource注解用法实例

    ~测试代码

     

@ConfigurationProperties

@ConfigurationProperties配置绑定

配置绑定就是使用Java读取配置文件中的内容,并将数据封装到JavaBean中,如数据库连接信息封装到数据源中,对于配置项上百行的配置文件有时需要使用正则表达式来进行匹配寻找,非常麻烦

~传统方式代码

springboot提供了两种配置绑定的方案

  1. 方案一:@ConfigurationProperties + @Component

    • 步骤一:在配置文件application.properties中对目标类Car依据属性进行了如下配置

    • 步骤二:确认目标类Car被纳入了IoC容器管理,只有被纳入IoC容器管理的组件才能享受类似配置绑定等其他spring强大功能

    • 步骤三:在目标类上使用@ConfigurationProperties(prefix = "mycar")注解通知springboot自动根据前缀mycar查找application.properties对应含有前缀的key如mycar.brand和mycar.price,并将值通过目标类的set注入注入属性值,注意value属性和prefix属性互为别名,使用哪一个都可以

      ~配置绑定代码

  2. 方案二:@EnableConfigurationProperties + @ConfigurationProperties

    • 步骤一:在配置文件application.properties中对目标类Car进行属性配置

    • 步骤二:在配置类上使用@EnableConfigurationProperties注解对配置类进行标注,注解的value属性为目标类的class对象,这一步的目的:

      • 根据class对象开启目标类的配置绑定功能

      • 把目标类这个组件自动注册到容器中

    • 步骤三:在目标类上使用@ConfigurationProperties注解指定前缀属性prefix进行配置绑定

       

自动配置原理

包扫描规则

@SpringBootApplication
  1. 核心注解@SpringBootApplication是一个复合注解,用来标注主程序类,该注解相当于@ComponentScan、@SpringBootConfiguration、@EnableAutoConfiguration三个注解的合成注解,从这三个注解的功能就能反应@SpringBootApplication的核心功能

@SpringBootConfiguration
  1. @Configuration注解是@SpringBootConfiguration元注解,表明@SpringBootConfiguration标注的类也是一个配置类,即主程序类是一个核心配置类

@ComponentScan
  1. 包扫描注解,指定要扫描哪些包,包扫描注解@ComponentScan有两个SpringBoot自定义的扫描器

    雷丰阳的spring注解视频中有介绍:尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)

@EnableAutoConfiguration
  1. @EnableAutoConfiguration也是一个合成注解,由注解@AutoConfigurationPackage以及组件导入@Import(AutoConfigurationImportSelector.class)构成

  2. @AutoConfigurationPackage

    @AutoConfigurationPackage注解的作用就是实现了默认包扫描范围为主程序类所在包及其所有子包

    • @AutoConfigurationPackage意为自动配置包,由其源码:@Import(AutoConfigurationPackages. Registrar.class),可知该注解就是给容器导入Registrar组件,通过Registrar组件的registerBeanDefinitions方法给容器导入一系列组件

    • Registrar类的registerBeanDefinitions方法分析

      • 通过Registrar组件的registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegister)方法执行批量导入组件的操作,这些组件即主程序类MainApplication所在包及其所有子包下的组件

      • AnnotationMetadata metadata是注解源信息,该注解指的是@AutoConfigurationPackage,注解的源信息包括了该注解的标注位置MainApplication的全限定类名以及该注解有哪些属性值

        [因为@AutoConfigurationPackage合成了注解@EnableAutoConfiguration,最终相当于@AutoConfigurationPackage注解标注在主程序类MainApplication上,在registerBeanDefinitions方法中new PackageImports(metadata).getpackageName(). toArray(new Array[0])通过元注解信息获取到组件所在的包名,即主程序类所在的包名,封装在数组中然后使用register方法进行注册,这就是默认包扫描范围在主程序类MainApplication所在包及其所有子包下的原因]

加载自动配置类

初始化时加载的相关自动配置类

  1. @Import(AutoConfigurationImportSelector.class)

    • @Import(AutoConfigurationImportSelector.class)注解是@EnableAutoConfiguration注解的组分之一

    • 利用selector机制引入AutoConfigurationImportSelector组件给容器批量导入组件

      • AutoConfigurationImportSelector中的selectImports方法返回的字符串数组中规定了要批量导入组件的列表,该列表是调用getAutoConfigurationEntry(annotationMetadata)获取的;重点就是方法:getAutoConfigurationEntry(annotationMetadata)

      • getAutoConfigurationEntry(annotationMetadata)方法解析:

        • 该方法获取所有需要自动配置的集合

        • getCandidateConfigurations(annotationMetadata, attributes)方法,作用是获取所有备选的配置,返回configurations;

        • 接下来依次对configurations移除重复的选项,排除一些配置选项,以及一些额外操作封装成AutoConfigurationEntry进行返回,在没有重复项和需排除项的情况下configurations中一共有127个组件,这127个组件默认需要导入容器,现在的重点变成了获取备选配置的方法getCandidateConfigurations(annotationMetadata, attributes)

        • getCandidateConfigurations(annotationMetadata, attributes)方法解析:

          • SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法使用Spring工厂加载器加载一些资源,重点是loadFactoryNames方法

          • loadFactoryNames方法中的重点是loadSpringFactories(classLoader)方法,利用loadSpringFactories方法加载得到一个Map集合,Map集合的value是一个List集合;这个Map集合中保存的就是所有的组件,只要看懂loadSpringFactories方法就能知道从哪里获取的所有组件

        • loadSpringFactories(classLoader)方法解析

          • classLoader.getResources("META-INF/spring.factories"):从META-INF/spring.factories位置加载一个文件,默认扫描当前系统中所有META-INF/spring.factories位置的文件,在引入的第三方jar包中,有些jar包有META-INF/spring.factories这个文件,如spring-boot、spring-boot-autoconfigure(最核心的);有些包则没有这个文件

            在最核心的jar包spring-boot-autoconfigure-2.3.4.RELEASE.jar中的META-INF/spring.factories中第21行开始配置了@EnableAutoConfiguration注解由@Import(AutoConfigurationImportSelector.class)引入的全部127个自动配置组件,即该文件这127行写死了spring-boot一启动就要给容器加载的所有配置类,这些类也都在spring-boot-autoconfigure这个jar包下

          • 虽然127个场景的所有自动配置启动的时候默认全部加载,但是最终会按需分配,通过IoC容器的组件数量只有135个,包括我们自己配置的组件和其他组件,显然127个组件并未全部生效

    • 组件按需配置机制

      • 在对应127个组件的自动配置类中都有派生条件装配注解,如:

        • AopAutoConfiguration有@ConditionalOnClass(Advice.class),意思是当类路径中存在Advice这个类这个组件才会生效,也即只有导入了aop相关的包aspectj,对应的组件才会生效

        • BatchAutoConfiguration有@ConditionalOnClass(JobLauncher.class,DataSource.class),也只有导入批处理的包,这个类下的组件才会生效

        • 总结,派生条件装配注解的value发红对应组件就不会生效

      • 启动全部加载,最终按照条件装配规则按需配置

自动配置流程

  1. 以AopAutoconfiguration分析自动配置功能(经确认AopAutoconfiguration确实在127个中)

  2. 以CacheAutoconfiguration分析自动配置功能(经确认CacheAutoconfiguration确实在127个中)

  3. 以DispatcherServletAutoconfiguration分析自动配置功能(经确认确实在127个中)

    web下的servlet包下有很多自动配置类,不止DispatcherServletAutoconfiguration

  4. HttpEncodingAutoconfiguration分析自动配置功能(经确认确实在127个中)

    请求参数和响应参数不乱码的原因就在于HttpEncodingAutoConfiguration

总结
  1. SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  2. 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。(xxxxProperties里面读取,xxxProperties和配置文件进行了绑定,在xxxxxAutoConfiguration中会大量看到@EnableConfigurationProperties(XxxxProperties.class)注解,而配置文件中各种配置代表什么意思官方文档中的Application Properties都可以查到,学好技术改配置比如使用redis就一句话的事)

  3. 生效的配置类会给容器中装配很多组件

  4. 只要容器中有这些组件,相当于这些功能就有了

  5. 只要用户有自己配置的,就以用户的优先,即定制化配置:

    • 办法一:用户直接自己用@Bean替换底层的组件

    • 办法二:用户查看目标组件获取的配置文件的对应值去属性配置文件自己修改即可,比如上述例子中的字符编码格式:

      • 点开对应的CharacterEncodingFilter类查看@ConditionalOnProperty注解的前缀prefix="server.servlet.encoding"

      • 在HttpEncodingAutoConfiguration中观察到通过this.properties.getCharset()方法获取属性值

      • 即属性配置文件的key对应为server.servlet.encoding.charset=GBK

  6. 自动配置原理全流程:

    • xxxxxAutoConfiguration加载自动配置类 ---> 根据条件注册组件 ---> 组件从xxxxProperties里面拿值 ----> xxxxProperties绑定的就是application.properties里面的值

 

最佳实践

SpringBoot开发技巧

  1. 引入场景依赖

    • 比如开发缓存、消息队列等首先看SpringBoot或者第三方有没有开发相关的场景依赖,第三方技术提供的starter和Spring官方提供的starter列表:官方文档(Using Spring Boot--->Starters)

  2. 查看自动配置了哪些组件(选做,因为比较偏底层原理,关心源码比较有用)

    • 自己分析,引入场景对应的自动配置一般都会生效,但是逐行分析比较麻烦

    • 配置文件中添加debug=true开启自动配置报告,会在控制台输出哪些配置类生效,哪些未生效,未生效会提示Did not match:不生效的原因

      • 控制台的Negative matches下展示未生效的组件列表

      • 控制台的Positive matches下展示生效的组件列表

  3. 修改配置项

    • 参考官方文档的Application Properties:官方文档

    • 自己到XxxxProperties查看绑定了配置文件的哪些配置然后自己分析更改

      ~示例修改spring.banner;banner是springboot启动时的图标,可以使用本地的图片替换,可以通过指定spring.banner.image.location的值,默认值是找classpath:banner.gif或者jpg或png;可以直接把图片的名字改成banner,也可以指定spring.banner.image.location=图片名.jpg;

    • 自己添加或者替换组件

      • 通过@Bean、@Component...,因为用户有的以用户优先

    • 自定义器 XXXXXCustomizer

      • 以后会讲

    • ...

Lombok简化开发

  1. 使用Lombok的步骤

    • 步骤一:添加Lomback依赖

    • 步骤二:IDEA中File->Settings->Plugins,搜索安装Lombok插件

    • 步骤三:使用Lombok

      • 通过注解@Data标注类在编译阶段生成setter/getter方法

      • 通过注解@ToString标注类在编译阶段生成toString方法

      • 通过注解@AllArgsConstructor标注类在编译阶段生成所有属性的有参构造器

      • 通过注解@NoArgsConstructor标注类在编译阶段生成无参构造器

      • 通过注解@EqualsAndHashCode标注类在编译阶段生成Equals和HashCode方法

      • 通过注解@Slf4j标注类自动给类添加log属性用来记录日志以简化日志开发

  2. SpringBoot2默认管理的lombok版本1.18.12,需要用户自己引入依赖,Lombok的作用是在程序编译阶段自动生成程序的setter/getter方法、toString方法、构造方法、Equals和HashCode方法

    ~Lombok依赖

  3. 用法实例

 

spring-boot-devtools

  1. 使用spring-boot-devtools的步骤

    • 步骤一:官方文档-->Using Spring Boot-->Developer Tools-->拷贝依赖信息

      ~dev-tools依赖

  2. spring-boot-devtools的作用是热更新,修改代码或者修改web页面后只需要按Ctrl+F9(作用是让项目重新编译一遍,编译完毕devtools就能自动帮助用户重新加载),页面就能实时生效;不需要再重启服务器。devtools的实质是自动重启服务器

    • 注意:如果没有修改任何资源,ctrl+f9不会生效

  3. 需要使用真正的热更新Reload需要使用付费的插件JRebel

 

Spring Initailizr

  1. Spring Initailizr的使用

    • Spring Initailizr是创建Spring Boot项目初始化向导,创建工程的时候不选择maven或者空工程,直接创建Spring Initailizr工程

  2. Spring Initailizr的作用

    • 在工程创建过程中就可以勾选想要引入的场景,想要使用的数据库,Mybatis,SpringBoot的版本等等

    • 创建工程后会自动生成pom.xml的场景启动器相关配置并且引入junit和打包插件

    • 自动创建全局配置文件、static目录(写所有的静态资源,即CSS、JS文件)、templates目录(写所有的视图页面)

    • 自动创建主程序类(以工程名+Application构成)

       

配置文件

yaml配置文件

SpringBoot兼容两种配置文件格式,properties文件和yaml,注意yaml的后缀可以是.yaml,也可以是.yml;SpringBoot中需要起名application.yml,如果两种类型的文件都有,两个文件都会生效,且同名配置以properties文件优先加载

YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),标记语言表示文件是用标签写的

YAML非常适合用来做以数据为中心的配置文件,即存储配置数据而不是定义一些行为动作,spring的配置首选yaml,优点就是能够清晰的看见属性配置的从属关系

 

基本语法

  1. 配置数据的格式

    • key: value;[所有冒号和value之间有空格,且key和value严格区分大小写]

  2. 使用缩进表示层级关系

    [XMl通过标签表示层级关系,而YAML通过缩进表示层级关系,缩进一般不允许使用tab,只允许使用空格,但是IDEA中可以使用tab空格数不重要,但是要保证相同层级的元素要对齐,不同层级的空格数可以不一样]

  3. '#'表示注释

  4. 字符串无需加引号,默认行为与加单引号相同,可以按需加单双引号

    • 单引号会转义字符串中的'转义字符',双引号不会转义字符串中的'转义字符'

 

数据类型

  1. 字面量:字面量是单个的、不可再分的值。对应的java类型有date、boolean、string、number、null

  2. 对象:表示对象有两种写法,对应的java对象有map、hash、set[set集合不是只有value吗,不应该在数组的表示方法中吗]、object

  3. 数组以及以数组为基础的集合:一组按次序排列的值。对应的java对象array、list、queue

     

yaml实例

  1. 目标类

  2. application.yml对应配置

 

配置注解处理器

注解处理器的作用是针对用户自定义类添加了@ConfigurationProperties注解后在属性配置文件中提供提示功能

~引入Configuration the Annotation Processer依赖

~排除Configuration the Annotation Processer依赖的插件打包行为

 

SpringBoot-Web开发

Web开发的官方文档位于Spring Boot Features--->Developing Web Application

Web开发简介

SpringMVC自动配置概览

  1. SpringBoot-Web开发为SpringMVC自动配置的组件列表

    [没有说全,只说了大部分内容]

    • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

      • 内容协商视图解析器和BeanName视图解析器

    • Support for serving static resources, including support for WebJars (covered later in this document)).

      • 静态资源的访问组件(包括webjars)

    • Automatic registration of Converter, GenericConverter, and Formatter beans.

      • 自动注册所有的转换器和格式化器【用来类型转换的转换器】

    • Support for HttpMessageConverters (covered later in this document).

      • 支持 HttpMessageConverters

        后来配合内容协商理解原理

    • Automatic registration of MessageCodesResolver (covered later in this document).

      • 自动注册 MessageCodesResolver

        国际化用,一般用不到,因为一般针对国内外用户做两套网站,因为语言和文化的区别

    • Static index.html support.

      • 静态index.html 页支持

        将欢迎页放到指定位置会自动支持欢迎页机制

    • Custom Favicon support (covered later in this document).

      • 自定义 Favicon

    • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

      • 自动使用 ConfigurableWebBindingInitializer

        DataBinder负责将请求数据绑定到JavaBean上,如WebDataBinder

      • 没有喜欢的也可以自定义数据绑定器

  2. SpringBoot自定义组件的三种方案

    If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

    不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

    If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

    声明 WebMvcRegistrations 改变默认底层组件

    If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

    使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

 

静态资源规则与定制化

静态资源指图片、视频、JS文件、CSS文件等等

静态资源访问

  1. 静态资源目录

    • 只要以下目录放在类路径下[resources目录下]都可以作为静态资源目录

      /static or /public or /resources or /META-INF/resources

  2. 静态资源的请求路径

  3. 静态资源的访问前缀

    • 静态资源的请求路径默认无前缀

    • 可以通过spring.mvc.static-path-pattern属性配置静态资源访问前缀,设置前缀后的请求路径为:

      当前项目根路径即/ +spring.mvc.static-path-pattern+ 带后缀的静态资源名【如:http://localhost:8080/res/1.png

  4. 改变默认的静态资源路径

    • 通过spring.web.resources.static-locations属性指定新的静态资源目录,指定的新目录可以是一个数组,即可以配置多个静态目录,此时默认静态资源目录除 /META-INF/resources外的确全部失效

  5. WebJars

    作用是把一些前端资源文件以jar包的方式作为maven依赖导入应用

    • 把常见的一些Bootstrap、JQuery、CSS等资源文件弄成了jar包,实际jar包解压之后里面还是原版资源文件,jar包可以作为依赖导入maven

      ~导入jquery的依赖

    • WebJars官网:https://www.webjars.org/

    • 访问地址:http://localhost:8080/webjars/jquery/3.5.1/dist/jquery.js项目根路径'/'后面地址webjars/jquery/3.5.1/dist/jquery.js是按照对应依赖jar包里面的类路径写的

欢迎页支持

SpringBoot支持两种欢迎页设置方式

  1. 静态资源目录下的index.html

    • 当设置了访问前缀,所有的欢迎页都会失效;未设置访问前缀,所有静态资源目录下的欢迎页均有效

  2. 使用Controller处理"/"跳转index.html

    • 这个暂时还没说

 

自定义Favicon

Favicon的作用是设置网页标签上的小图标,访问该应用的任何请求都会展示该小图标,小图标是浏览器显示在Title前面的图标

  1. 设置方式

    • 将目标小图标命名成favicon.ico放在静态资源目录下即可

      【注意设置了访问前缀会导致Favicon功能失效,添加了静态资源应该clear和package一下,避免资源没有打包到target】

 

静态资源配置原理

  1. SpringBoot启动默认加载自动配置类xxxAutoConfiguration类,相关的xxxAutoConfiguration有:

    • DispatcherServlertConfiguration【配置DispatcherServlet规则的】

    • HttpEncodingAutoConfiguration【配置编解码的】

    • MultipartAutoConfiguration【配置文件上传的】

    • ServletWebServerAutoConfiguration【配置服务器的】

    • WebMvcAutoConfiguration【这个是SpringMVC的自动配置,SpringMVC的功能大多集中在这个类中】

  2. WebMvcAutoConfiguration源码解析

    • 重点一:配置类组件WebMvcAutoConfigurationAdapter中进行配置绑定的两个类

      • WebMvcProperties:绑定spring.mvc前缀的配置属性

      • ResourceProperties:绑定spring.spring.resources前缀的配置属性

    • 重点二:WebMvcAutoConfiguration配置类给容器中配置的组件

      • ResourceProperties resourceProperties:获取和spring.resources配置绑定所有值的对象

      • WebMvcProperties mvcProperties:获取和spring.mvc配置绑定所有值的对象

      • ListableBeanFactory beanFactory:Spring的beanFactory(bean工厂),即IoC容器 以下ObjectProvider<Xxx>表示找到容器中的所有Xxx组件

      • ObjectProvider<HttpMessageConverters>:找到所有的HttpMessageConverters

      • ObjectProvider<ResourceHandlerRegistrationCustomizer>:找到资源处理器的自定义器

      • ObjectProvider<DispatcherServletPath>:找DispatcherServlet能处理的路径

      • ObjectProvider<ServletRegistrationBean<?>>:找到所有给应用注册Servlet、Filter、Listener的组件

    • 重点三:静态资源的默认处理规则

      • WebJars对应静态资源的相关规则

        • 如果有/webjars/**请求,去类路径下找/META-INF/resources/webjars/,且相应静态资源应用缓存策略,缓存时间由spring.resources.cache.period配置属性控制

      • 静态资源路径的相关配置规则

        • 通过spring.mvc.static-path-pattern属性获取静态资源目录是否有前缀,没配置也有默认值staticPathPattern=/**,即匹配所有请求,通过this.resourceProperties.getStaticLocations()去找静态资源,staticLocations是一个字符串数组,有四个字符串默认值:被定义成常量的字符串数组{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" },即静态资源默认的四个位置,且这里的静态资源也有缓存策略

    • 重点四:欢迎页处理规则

      • 通过spring.mvc.static-path-pattern属性(指定静态资源请求前缀,默认是没有前置,即/**)创建welcomePageHandlerMapping

      • 如果欢迎页存在且静态资源请求路径没有前缀,则重定向到静态资源目录的index.html,所以有自定义静态资源请求前缀欢迎页就找不到了,在创建对象welcomePageHandlerMapping的第一个if中写死了

      • 否则如果欢迎页存在,但是staticPathPattern不为/**,直接设置viewName为index转到Controller看是否存在控制器方法能处理"/index"请求

    WebMvcAutoConfiguration的源码逐行解析

    WelcomePageHandlerMapping欢迎页处理映射器构造方法的代码逐行解析

     

请求参数处理

Rest风格请求映射原理

  1. @RequestMapping的派生注解

    • @GetMapping【相当于@RequestMapping(value = "/user",method = RequestMethod.GET)

    • @PostMapping【~@RequestMapping(value = "/user",method = RequestMethod.POST)

    • @PutMapping【~@RequestMapping(value = "/user",method = RequestMethod.PUT)

    • @DeleteMapping【~@RequestMapping(value = "/user",method = RequestMethod.DELETE)

  2. Rest风格支持【使用同一请求路径不同的HTTP请求方式动词来区分对资源的操作

    • 以前:[/getUser 获取用户],[/deleteUser 删除用户],[/editUser 修改用户],[/saveUser保存用户]

    • 现在:/user [GET-获取用户],[DELETE-删除用户],[PUT-修改用户],[POST-保存用户]

    • Rest风格支持核心的核心Filter组件:HiddenHttpMethodFilter

      • SpringBoot默认就配置了HiddenHttpMethodFilter组件,其实是其子类OrderedHiddenHttpMethodFilter

  3. HiddenHttpMethodFilter组件的用法

    • 步骤一:通过前端页面form表单发送post请求,设置隐藏域类型的_method参数为put、delete发送PUT或DELETE请求,GET请求和POST请求正常发,实现前端Rest风格请求的发送准备

    • 步骤二:通过在全局配置文件中配置spring.mvc.hiddenmethod.filter.name=true开启页面表单的隐藏请求方式过滤器组件的功能

    • 步骤三:编写控制器方法处理Rest风格请求映射

  4. form表单提交使用REST风格的原理

    • 发送put和delete请求表单以post方式提交并添加请求参数'_method',即开启页面表单的Rest功能

    • 请求被服务器捕获并被HiddenHttpMethodFilter拦截

      • 请求方式是否为post且请求没有错误和异常

        • 获取表单请求参数_method的属性值

          • 如果属性值不为空串或者null则全部转换为大写字母

          • 判断_method的属性值是否为PUT、DELETE、PATCH三者之一

            • 是就创建原生请求对象的包装类,将method的属性值赋值给包装类自己的method属性中,通过重写getMethod方法将获取请求方式的值指向method的属性值

      • 如果请求方式不为post直接放行原生请求,如果是post就放行请求的包装类

    • 相关源码

    配置spring.mvc.hiddenmethod.filter.name=true的源码

    使用表单post请求并添加_method属性的源码

  5. 可以使用客户端工具或者Ajax直接发送put或者delete请求

    在支持发送真实put、delete请求的场景下就没有必要使用包装类HttpMethodRequestWrapper了,此时请求方式会直接为put或者delete,直接把request赋值给requestToUse,然后立即放行requestToUse

    • PostMan可直接发送put或者delete请求

    • 安卓直接发put或者delete请求

    • Ajax直接发put或者delete请求

  6. 扩展点:如何把_method这个名字换成用户自定义的

    • 要点

      • HiddenHttpMethodFilter组件如果有SpringBoot就不再自动装配,用户可以自己配置

      • HiddenHttpMethodFilter中的setMethodParam方法可以设置methodParam用自定义参数名代替_method

       

请求映射原理

SpringBoot底层处理请求还是使用SpringMVC,DispatcherServlet是所有请求处理的开始,DispatcherServlet又名前端控制器,分发控制器

DispatcherServlet继承于FrameworkServlet,FrameworkServlet继承于HttpServletBean,HttpServletBean继承于HttpServlet

HttpServlet中没有重写doXxx方法,这一类方法的重写在子类FrameworkServlet中完成,而且统一都去调用自己的processRequest方法,processRequest核心方法是doService方法,执行doService方法前后分别是初始化过程【调用setter/getter方法】和日志处理过程,FrameworkServlet中的doService方法是一个抽象方法,在DispatcherServlet子类中进行了实现

DispatcherServlet中doService方法的核心方法是doDispatch方法【doDispatch意为给请求做派发】,也是请求处理的核心逻辑方法,每一个请求进来都会调用doDispatch方法

doDispatch()源码解析

doDispatch方法源码解析

注意以下方法都是从doDispatcher方法中进入延伸

 

getHandler()

重点1:getHandler(processedRequest)源码解析

  1. mappedHandler = getHandler(processedRequest)的底层原理

    • 所有的请求映射都在应用启动时被解析放入各个HandlerMapping中

    • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,配置了"/"对应的映射规则:view= "forward:index.html",能处理访问路径" /"转发到index.html

    • SpringBoot自动配置了默认的RequestMappingHandlerMapping,在mappingRegistry属性【映射的注册中心】中保存了用户自定义请求路径和控制器方法的映射规则,可以处理控制器方法的对应请求路径

    • 请求处理流程

      • 请求进来,for增强循环遍历所有的HandlerMapping匹配请求映射信息,找得到就返回对应控制器方法的Handler【Handler中封装了控制器方法的所有信息】,找不到就返回null继续遍历下一个HandlerMapping

  2. 自定义处理器映射

    • 处理器映射容器中不止一个,用户可以根据需要自行给容器中配置HandlerMapping组件

    • 比如访问api文档,根据不同api版本设计不同的请求路径,不仅通过控制器来处理请求,还可以通过处理器映射指定请求映射规则来派发不同api版本的请求的对应资源路径

mappedHandler = getHandler(processedRequest)寻找处理当前请求的Handler的原理

mapping.getHandler(request)方法中的getHandlerInternal(request)方法解析

getHandlerInternal(request)方法中的super.getHandlerInternal(request)方法解析

super.getHandlerInternal(request)方法中的lookupHandlerMethod(lookupPath, request)方法解析

getHandlerAdapter()

重点2:getHandlerAdapter(mappedHandler.getHandler())源码解析

  1. 对默认的四个处理器适配器进行for增强遍历,使用四个不同适配器的supports方法对handler的类型进行匹配,@RequestMapping注解标注的方法对应Handler是HandlerMethod类型,函数式编程方法对应的是HandlerFunction类型,类型匹配就返回对应的适配器

doDispatch方法中的getHandlerAdapter方法解析

ha.handle()

重点3:ha.handle(processedRequest, response, mappedHandler.getHandler())源码解析

执行handler目标方法的适配器handle方法源码解析

  1. handle方法核心流程总结:

    • 方法1:DispatcherServlet的doDispatch方法的handle方法

      • 调用handleInternal方法获得mav对象【ModelAndView】

    • 方法2:AbstractHandlerMethodAdapter的handle方法的handleInternal方法

      • 调用invokeHandlerMethod方法获取参数解析器解析参数,执行控制器方法将返回值封装到ModelAndView并返回ModelAndView

    • 方法3:RequestMappingHandlerAdapter的handleInternal方法的invokeHandlerMethod方法

      • 构建webRequest,放入doDispatch方法传参的request和response

      • 获取目标方法对象并封装成可执行方法对象,可执行方法对象可以调用invokeAndHandle方法

      • 为可执行方法对象设置所有的参数解析器和返回值处理器

      • 调用invokeAndHandle方法获取参数解析器解析参数,执行控制器方法将返回值【即视图信息】封装到mavContainer中并获取到模型和视图容器对象

      • 调用getModelAndView方法使用mavContainer获取ModelAndView对象并返回给handleInternal方法

    【分支1:invokeAndHandle方法分支】

    • 方法4:RequestMappingHandlerAdapter的invokeHandlerMethod方法的invokeAndHandle方法

      无需返回值,直接把操作结果传入参数mavContainer的defalutModel和view属性中

      • 调用invokeForRequest方法去找参数解析器解析参数,把请求域共享数据放进mavContainer的属性defaultModel,并调用目标控制器方法,完事后得到控制器目标方法的返回值存入returnValue

      • 调用handleReturnValue方法处理控制器方法的返回结果,其实是把返回值的字符串或者其他东西处理赋值给mavContainer的view属性

    【小分支1:invokeForRequest方法分支】

    • 方法5:ServletInvocableHandlerMethod的invokeAndHandle方法的invokeForRequest方法

      • 用一个Object数组接受调用getMethodArgumentValues方法获取的所有的形参对应的实参值,根据形参顺序排列

      • 调用doInvoke方法传参Object数组使用反射机制完成目标控制器方法的调用并向invokeAndHandle方法返回控制器方法的返回值

    • 方法6:InvocableHandlerMethod的invokeForRequest方法的getMethodArgumentValues方法

      • 获取目标方法的所有形式参数的详细信息,包括形式参数的注解、索引位置、参数类型等并创建对应参数长度的object数组准备接收解析出来的参数值,最后返回的就是接收完所有实参的Object数组

      • 有参数就对参数进行遍历,遍历过程中

        • 调用参数解析器集合的supportsParameter方法匹配对应参数的参数解析器,并将参数解析器以参数对象为key存入参数解析器缓存

        • 调用参数解析器集合的resolveArgument方法解析参数的值并将值顺序放入Object数组

      • 遍历完事以后返回Object数组

    【小小分支1:参数解析器集合的supportsParameter方法分支】

    • 方法7:InvocableHandlerMethod的getMethodArgumentValues方法的supportsParameter方法

      • 调用getArgumentResolver方法遍历所有参数解析器,并调用各个参数解析器的supportsParameter方法判断是否支持解析当前参数,支持就把对应的参数解析器存入argumentResolverCache即参数解析器缓存对象中,并返回参数解析器给supportsParameter方法,该方法判断参数解析器不为空就返回true,为空就返回false

    【小小分支2:参数解析器集合的resolveArgument方法分支】

    • 方法8:InvocableHandlerMethod的getMethodArgumentValues方法的resolveArgument方法

      参数解析器集合调用的resolveArgument方法

      • 尝试从缓存中获取对应的参数解析器,获取不到就去遍历参数解析器集合重新获取参数解析器并存入缓存

      • 调用对应的参数解析器的resolveArgument方法解析出参数值并返回被放入Object数组

    • 方法9:HandlerMethodArgumentResolverComposite中的参数解析器集合的resolveArgument方法的参数解析器的resolveArgument方法

      • 通过参数获取参数的名字

      • 调用resolveName传入参数的名字获取参数值,实际是urlPathHelper提前按正则匹配的方式处理URL解析所有的数据封装成map集合uriTemplateVars,参数解析器获取参数都是拿着参数名直接从Map集合里面取,然后返回取得的参数值,最终封装在Object数组中

    【小分支2:handleReturnValue方法分支】

    • 方法10:ServletInvocableHandlerMethod的invokeAndHandle方法的handleReturnValue方法

      • 调用selectHandler方法根据返回值类型找到合适的返回值处理器并赋值给handler

      • 处理器handle对象调用对应的handleReturnValue方法处理控制器方法的返回值

    • 方法11:HandlerMethodReturnValueHandlerComposite的handleReturnValue方法的handleReturnValue方法

      • 如果Object类型的返回值是一个字符串,将返回值转为字符串并存入mavContainer对象的view属性,此时mavContainer【模型和视图的容器】对象中既有向请求域中共享的defaultModel中的数据,也有了view数据;

      • 如果viewName是重定向视图名就把mavContainer对象的redirectModelScenario属性设置为true

    【分支2:getModelAndView方法分支】

    • 方法12:RequestMappingHandlerAdapter的invokeHandlerMethod方法的getModelAndView方法方法

      • 进来就有一个updateModel方法是设置model中共享数据绑定策略的,没听明白,暂时不管

      • 从mavContainer获取defaultModel,把defaultModel、view属性值还有状态码传入ModelAndView对象的有参构造来创建ModelAndView对象mav

      • 如果Model是重定向携带数据,调用putAll方法把所有数据放到请求的上下文中【?应用上下文吗?,没听明白这里】,最后返回mav给invokeHandlerMethod方法

doDispatch方法中的ha.handle方法解析

handle方法中的handleInternal方法解析

handleInternal方法中的invokeHandlerMethod方法解析

  1. 对可执行方法对象设置参数解析器和返回值处理器

  2. 调用可执行方法对象的invocableMethod.invokeAndHandle(webRequest, mavContainer);方法执行目标方法并返回封装好请求域共享数据和视图名称的ModelAndView对象,注意此时如果不是转发就还没有向请求域共享数据,是转发就已经把共享数据放在上下文对象中了

invokeHandlerMethod方法中的invokeAndHandle方法解析


  1. 进入invokeAndHandle方法后立即通过invokeForRequest方法去执行控制器目标方法

invokeAndHandle方法中的invokeForRequest方法解析



  1. 核心1:通过getMethodArgumentValues方法去获取参数解析器,使用参数解析器获取形参参数值打包成Object数组

  2. 核心2:带着所有形参参数通过doInvoke(args)方法去执行控制器方法,具体用反射调用目标方法的过程不管,重点是执行完以后怎么办,显然这里return的就是控制器方法的返回值返回给invokeAndHandle方法并赋值给returnValue

invokeForRequest方法的getMethodArgumentValues方法解析

  1. 以下代码就是如何确定每一个目标参数的值的代码

    • 小重点1:判断和寻找每个参数的参数解析器

    • 小重点2:解析参数的参数值

  2. 核心就是获取目标方法的所有参数信息数组,对参数使用增强for循环;对每个参数都使用参数解析器集合的参数支持判断方法对每一个参数解析器循环遍历调用相应的参数支持判断方法根据注解类型和参数类型匹配参数解析器并将参数解析器放在参数解析器缓存中;然后对每个参数使用参数解析器集合中的解析参数方法直接从缓存中获取参数解析器,如果获取不到再去遍历所有的参数解析器[一层通用行为],再通过参数解析器的解析参数方法一般都从请求域中拿数据(可能从请求域中直接拿,也可能从请求域中封装好的Map里面来,具体要看每种参数解析器的实现)[参数解析器的个体行为]

小重点1:以下是判断和寻找每个参数的参数解析器的过程解析




【getMethodArgumentValues方法中的resolvers.supportsParameter方法解析】

  1. 重点:形参对应的参数解析器缓存机制,第一次请求去挨个遍历26个参数解析器,速度慢;找到以后就以参数作为key,参数解析器作为value存入参数解析器缓存argumentResolverCache,以后请求找参数解析器都走缓存

getArgumentResolver方法中的resolver.supportsParameter(parameter)方法解析

小重点2:以下是解析当前参数的参数值的解析过程




getMethodArgumentValues方法中的resolvers.resolveArgument方法解析

【resolveArgument方法中的resolver.resolveArgument方法解析】

resolveArgument方法中的resolveName方法解析

invokeAndHandle方法中的this.returnValueHandlers.handleReturnValue方法解析



handleReturnValue方法中的handler.handleReturnValue方法

invokeHandlerMethod方法中的getModelAndView方法解析


  1. 共享数据和视图名称都放在了ModelAndViewContainer类型的mavContainer对象中,包含要去的页面地址View和请求域共享的Model数据,以下的代码就是对mavContainer对象中的相关数据进行处理

mavContainer

getModelAndView方法中的updateModel方法解析

processDispatchResult()

重点4:processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)源码解析

在该方法中把Model中的数据放到请求域中,核心是在最后渲染那一步才把model中的数据封装到LinkedHashMap类型的mergedMap对象中,在究极嵌套的render方法的view.render方法中的最后一个方法renderMergedOutputModel方法中的exposeModelAsRequestAttributes方法中放入请求域的

【doDispatch方法中的processDispatchResult方法解析】

小重点1:processDispatchResult方法中的render方法

【render方法中的resolveViewName方法解析】



【所有的视图解析器】

【resolveViewName方法中的viewResolver.resolveViewName(viewName, locale)方法解析】

【resolveViewName方法中的getCandidateViews方法】

【render方法中的view.render方法解析】



【view.render方法中的renderMergedOutputModel方法解析】

【renderMergedOutputModel方法中的exposeModelAsRequestAttributes方法】

 

Web开发核心对象
HandlerMapping

HandlerMapping【处理器映射

HandlerMappings

  1. 【/user—GET请求】下的HandlerMapping,请求处理规则【即/xxx请求对应处理器】都被保存在HandlerMapping中,默认有五个HandlerMapping

  2. WelcomePageHandlerMapping欢迎页的处理器映射,老熟人了

    • WebMvcAutoConfiguration中的welcomePageHandlerMapping通过spring.mvc.static-path-pattern属性创建welcomePageHandlerMapping组件并配置到容器中

    • WelcomePageHandlerMapping中的pathMatcher路径匹配保存的是"/",意思是请求直接访问"/"会被对应到rootHandler中的view="forward:index.html",这就是HandleMapping中保存的映射规则

  3. RequestMappingHandlerMapping【@RequestMapping注解的所有处理器映射】

    • RequestMappingHandlerMapping保存了所有@RequestMapping注解和handler的映射规则,也由配置类WebMvcAutoConfiguration对该组件进行配置

    • 应用一启动,SpringMVC自动扫描所有的控制器组件并解析@RequestMapping注解,把所有注解信息保存在RequestMappingHandlerMapping中

    • RequestMappingHandlerMapping中有一个mappingRegistry属性【映射的注册中心】,其下的mappingLookup属性中用HashMap保存了用户所有自定义路径以及对应的处理器以及系统自带的两个错误处理映射

      mappingRegistry属性【映射的注册中心】

 

HandlerAdapter

HandlerAdapter【处理器适配器】

处理器就是一个大的反射工具,可以使用反射机制执行用户的控制器方法并且调用参数解析器为控制器方法的参数赋值

在这里插入图片描述

  1. RequestMappingHandlerAdapter:支持控制器方法上标注@RequestMapping注解的Handler的适配器

  2. HandlerFunctionAdapter:支持控制器函数式编程的方法【后面了解】

     

ArgumentResolver

ArgumentResolver【参数解析器】

  1. 在处理器适配器RequestMappingHandlerAdapter中有argumentResolvers属性,里面存储了26个参数解析器ArgumentResolver(数量不一定,我这里测试有27个,版本也不同),每个参数解析器都实现了HandlerMethodArgumentResolver接口

  2. 这些参数解析器的作用是自动设置将要执行的目标方法中形参的具体参数值

  3. 具体的参数解析器

    • RequestParamMethodArgumentResolver:解析标注@Requestparam注解请求参数的方法参数解析器

    • PathVariableMethodArgumentResolver:解析标注@PathVariable注解的请求参数

    • ...

    SpringMVC目标方法中能写多少中参数类型取决于参数解析器

  4. 参数解析器的源码

    • 由源码可知参数解析器的工作流程

      • 步骤1:判断当前解析器是否支持传入的当前参数

      • 步骤2:如果支持当前参数的解析就调用resolveArgument方法来进行解析

    【参数解析器作为处理器适配器的属性】

    【参数解析器的设计源码】

    有二十几个参数解析器,针对不同的参数注解和类型都有不同的实现,执行流程和代码原理在【重点3:ha.handle方法源码解析】中已经讲的很清楚了,去看就完事了

    【参数解析器实现的接口HandlerMethodArgumentResolver】

     

ReturnValueHandler

ReturnValueHandler【返回值处理器】

  1. 在处理器适配器RequestMappingHandlerAdapter中有returnValueHandlers属性,里面存储了15个返回值处理器ReturnValueHandler(我这里测试有15个,版本不同)【在可执行方法中也有returnValueHandlers属性】

  2. 这些返回值处理器决定了控制器方法的返回值类型种类(注意非形参)

    • 如返回ModelAndView、Model、View、返回值可以使用@ResponseBody注解、HttpEntity等等

    • SpringMVC支持的返回值类型【根据返回值处理器的顺序依次向下】

      • ModelAndView

      • Model

      • View

      • ResponseEntity

      • ResponseBodyEmitter

      • SreamingResponseBody【返回值类型是不是流式数据的,这是一个函数式接口】

      • HttpEntity【且返回值类型不能是RequestEntity】

      • HttpHeaders

      • Callable【判断是否异步的,将JUC的时候会讲】

      • DeferredResult【支持异步的】、ListenableFuture、CompletionStage【这三个都是被SpringMVC包装了的一些异步返回方式】

      • WebAsnyTask

      • 控制器方法上有@ModelAttribute注解标注的对应返回值【注意使用了这种注解还会判断返回值不是简单类型如字符串,必须是对象】

      • 控制器方法上有@ResponseBody注解标注的对应返回值

        • 【相应返回值处理器:RequestResponseBodyMethodProcessor】,这个返回值处理器就能处理响应json格式的数据

  3. 返回值处理器都继承了接口HandlerMethodReturnValueHandler

  4. 一些返回值处理器既可以作为返回值处理流程中的返回值处理器,也可以作为参数解析器

    以返回值处理器RequestResponseBodyMethodProcessor为例

    【继承结构图】

WebDataBinder

WebDataBinder【Web数据绑定器】

  1. 使用绑定工厂创建一个Web数据绑定器WebDataBinder类型的binder对象,控制器方法中的自定义对象参数创建的空pojo对象会被直接封装到binder对象中作为binder的target属性

  2. binder中还有一个conversionService属性称为转换服务,conversionService对应的WebConversionService对象中的converters属性里面封装者124个converter

    • Http协议规定传过来的都是字符串,SpringMVC就依靠这些转换器把String类型的数据转成各种类型的数据

    • 转换器不只是String类型转成其他类型的数据,各种数据类型的转换都有

 

MessageConverter

MessageConverter【消息转换器】

  1. 所有的MessageConverter都实现了HttpMessageConverter接口

    • canRead是判断能不能把Class类型的返回值对象以MediaType的格式读入消息转换器

    • canWrite是判断能否把Class类型的返回值对象以MediaType的格式写出到浏览器

      例子:Person对象转为JSON,还可以实现请求中的JSON转换成Person对象,是可逆的,把服务器对象转成媒体类型,把请求的媒体类型转成服务器对象,原因是某些返回值处理器如RequestResponseBodyMethodProcessor既可以作为返回值处理流程中的返回值处理器,也可以作为参数解析器,里面都有消息转换器

  1. 系统中默认的所有MessageConverter

    • ByteArrayHttpMessageConverter-只支持返回值Byte类型

    • StringHttpMessageConverter-只支持返回值String类型

    • StringHttpMessageConverter-只支持返回值String类型

    • ResourceHttpMessageConverter-只支持返回值Resource类型【Resource类型是springFramework的io下的InputStream,返回这个类型的也可以写出去,这个是一个接口,继承接口有HttpResource,HttpResource的实现类GzippedResource...是以压缩包的内容类型响应;FileNameVersionedResource是以文件的内容类型响应;AbstractResource是Resource的实现类,里面有N多个继承类,有路径的方式、压缩包的方式、系统文件的方式】【指定返回值类型就会自动调用相关的消息转换器】

    • ResourceRegionHttpMessageConverter-只支持返回值ResourceRegion类型

    • SourceHttpMessageConverter-支持的是一个返回值类型HashSet集合,里面的元素有【添加到set集合中是静态代码块中添加的】

      • DOMSource、SAXSource、StAXSource、StreamSource、Source

    • AllEncompassingFormHttpMessageConverter-只支持返回值MultiValueMap类型

    • MappingJackson2HttpMessageConverter-这个的support方法直接返回true,没有像其他转换器一样进行类型匹配,但是仍然有canWrite方法内部的重载方法canWrite方法的调用判断,两个都同时满足才满足对应返回值类型,一般来说这个消息转换器能处理任何对象将其转成json写出去

    • MappingJackson2HttpMessageConverter-这个和上一个是一样的,区别暂时没讲

    • Jaxb2RootElementHttpMessageConverter-只支持方法标注了@XmlRoctElement注解的返回值

 

HandlerInterceptor

HandlerInterceptor【拦截器】

  1. 所有的拦截器都继承了接口HandlerInterceptor

    包括自定义的拦截器,根据执行时机的需要选择合适的方法

    • 接口HandlerInterceptor中有三个需要被实现的方法

      • preHandle方法:在控制器方法执行前被调用执行

      • postHandle方法:控制器方法执行完但还没到达页面以前执行postHandle方法【即执行完handle方法获取ModelAndView后派发最终结果前】

      • afterCompletion方法:页面渲染完成后还想执行一些操作

  2. 自定义拦截器需要通过配置类实现WebMvcConfigurer接口的addInterceptors方法添加到IoC容器中

    • 通过registry.addInterceptor方法添加自定义的拦截器组件到IoC容器中

    • 在registry.addInterceptor方法后使用addPathPatterns方法添加拦截器生效的路径

      • /**表示所有请求,拦截所有也会拦截掉静态资源的访问

    • 在addPathPatterns方法后使用excludePathPatterns方法添加排除拦截器生效的路径

      • 正常写,如登录页面"/","/login'',以及静态资源如"/css/**",...

      • 静态资源的放行还可以通过设置静态资源前缀如/static,放行"/static/**"来实现对所有静态资源的放行

  3. 拦截器源码分析

    【拦截器原理总览】

    • 第一步:根据当前请求,找到HandlerExcutionChain【即doDispatch方法中的mappedHandler】

      其中包含处理请求的Handler以及对应请求的所有拦截器

      • 找到适配的Handler,即mappedHandler,其中的两个属性handler指向控制器的main.html映射匹配的控制器方法,mappedHandler实际上是一个HandlerExcutionChain,即处理器执行链

        处理器中只有两个属性,处理器和拦截器列表

      • interceptorList为拦截器列表,其中的LoginInterceptor就是自定义的登录验证拦截器

        • 下面两个拦截器任何方法都会执行

          • ConversionServiceExposinginterceptor

            • ResourceUrlProviderExposingInterceptor

    • 第二步:先顺序执行所有拦截器的preHandle方法

      • 如果当前拦截器preHandle方法返回true,则执行下一个拦截器的preHandle方法

      • 如果当前拦截器返回false,则直接触发triggerAfterCompletion方法倒序执行所有已经执行了preHandle方法的拦截器的AfterCompletion方法并返回false触发结束doDispatch方法的执行,不再继续执行目标控制器方法

    • 第三步:执行完handle方法并处理默认视图名字【没有视图名就应用默认视图名】后立即去倒序执行所有拦截器的postHandle方法

    • 第四步:执行完postHandle方法后立即去执行派发最终结果方法,在processDispatchResult方法的最后一行即页面成功渲染完成以后也会倒序触发执行已执行拦截器的afterCompletion方法

      以上所有过程发生任何异常都会捕捉异常直接去倒序执行所有执行过preHandle方法的拦截器的afterCompletion方法,包括自己写的拦截器方法发生异常

      【分支1:applypreHandle方法分支】

      【doDispatch方法中的applypreHandle方法】

      【applyPreHandle方法中的triggerAfterCompletion方法】

      【分支2:applyPostHandle方法分支】

      【doDispatch方法中的applyPostHandle方法】

Handler请求参数处理

请求参数基本注解

在SpringMVC中的控制器方法形式参数面前加上适当的注解,SpringMVC就可以自动为赋值目标请求参数

 

@PathVariable

路径变量注解@PathVariable可以从Rest风格请求路径获取请求参数并将特定请求参数与形参对应

  1. 在形参前面使用@PathVariable("id")能指定形参对应请求参数的位置并将请求参数赋值给形参

  2. 使用key和value都为String类型的Map集合作为形参结合@PathVariable注解能获取请求映射注解匹配路径中所有用大括号进行标识的请求参数

[使用@PathVariable注解的前提是请求映射@GetMapping("/car/{id}/owner/{username}")的匹配路径对相关请求参数用大括号进行标识,注意请求映射上大括号标注的路径变量可以动态的被替换]

 

@RequestHeader

获取请求头对象,可以通过请求头的Host获取请求发送的ip地址、通过User-Agent获取发送请求的浏览器信息

  1. 在形参前面使用@RequestHeader("User-Agent")可以获取请求头中key为User-Agent的单个属性值并赋值给对应字符串形参,这种方式只支持获取请求头中的单个键值对的属性值

  2. 使用key和value都为String类型的Map集合或者MultiValueMap类型以及HttpHeaders类型的形参结合@RequestHeader注解可以获取全部的请求头信息

 

@RequestParam

非Rest风格传递请求参数使用@RequestParam指定形参与请求参数名的对应关系

  1. 在形参前面使用@RequestParam("age")注解指定请求参数age与形式参数age的对应关系并自动赋值给对应的形式参数,如果请求参数中含有多个同名参数值,需要使用List集合进行接收

  2. 使用key和value都为String类型的Map集合结合@RequestParam注解可以获取所有的请求参数,注意这种使用map集合接收多个同名参数的方法存在参数值丢失的现象

  3. 使用@RequestParam注解可以通过指定required属性为true来要求请求必须携带指定参数

     

 

@CookieValue

使用@CookieValue注解可以获取请求的Cookie值

  1. 在字符串形参前面使用@CookieValue("JSESSIONID")注解指定请求头中Cookie项下的key为JSESSIONID的参数的参数值并为形参赋值,这种方式只是拿到了JSESSIONID的cookie值

  2. 在Cookie类型的形参前面使用@CookieValue("JSESSIONID")注解可以获取整个"JSESSIONID"的Cookie对象并赋值给形参,可以获取Cookie对象的名字,值等信息

注意Chrome需要先获取一次session对象才会生成JSESSIONID,否则在请求头是不会显示Cookie的

 

@RequestBody

使用@RequestBody注解可以获取POST请求的请求体,即表单中的数据

  1. 在字符串形参前面使用@RequestBody注解指定form表单中的数据并为形参赋值,浏览器请求体中表单的内容原本就是类似于Get请求URL中问号的部分如:username=zhangsan&email=2625074321%40qq.com,所以这种方式获取的字符串也是同样的组织形式

    • 请求体中封装的数据想要封装成自定义VO类,VO类前面必须添加@RequestBody注解

    • 注意,原生HTML中表单的数据提交类似于GET请求在URL后面拼接参数的方式提交,这种方式提交的参数不在请求体中,数据封装成自定义的VO类不需要在VO类前面添加@RequestBody注解,

      • 注意原生浏览器表单提交的数据不需要加@RequestBody注解,什么注解都不需要加,加了反而会报错Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]

      • 但是我在网址上没有看到拼接的参数啊,这一块要好好注意一下

  1. 原生HTML表单提交不能使用@RequestBody注解的示例

    • 加了反而会报错,这里复习的时候注意一下

     

 

@RequestAttribute

使用@RequestAttribute注解可以在转发页面获取请求域中的共享数据

  1. 在转发控制器方法中与请求域参数类型对应的形参前面使用@RequestAttribute("msg")注解指定域中的key为msg的值并自动为对应的形参赋值,要求对应的值转发必须已经存入请求域

  2. 从请求域中获取共享数据除了使用@RequestAttribute注解的方式外还可以使用原生的被转发的request请求调用getAttribute获取,你懂的

  3. 可以在@RequestAttribute注解中使用required=false指定参数不是必须的,没传参引用数据类型全部默认null

 

@MatrixVariable

使用@MatrixVariable注解可以获取矩阵变量路径中的矩阵变量

  1. 矩阵变量

    矩阵变量是一种请求数据的组织形式,原来一直使用的是查询字符串

    • 查询字符串【queryString】:/cars/{path}?xxx=xxx&xxxx=xxx

      • 查询字符串的方式使用@RequestParam注解获取请求数据

    • 矩阵变量:/cars/sell;low=34;brand=byd,audi,yd

      • 应用场景:Cookie禁用,浏览器请求头不携带JSESSIONID,解决办法是重写url,把cookie的值使用矩阵变量的方式进行传递:/abc;jsessionid=xxxx

      • 含多个同名参数的矩阵变量写法:

      • 矩阵变量中/xxx;xxx=xxx/的xxx;xxx=xxx是一个基本整体,一个请求路径中可以有多个这样的基础单元;基本单元中分号前面没有等号的部分是访问路径,分号后面的等式是矩阵变量,多个矩阵变量间使用分号进行区分

    • 使用@MatrixVariable注解可以获取矩阵变量路径中的矩阵变量,注意这种获取矩阵变量的方式矩阵变量必须绑定在路径变量中,即请求映射注解中的矩阵变量部分基本单元需要使用{xxx}如{path}代替,多个基本单元中含有同名变量,需要在@MatrixVariable注解中使用pathvar("xxx")如pathVar("path")指定基本单元

  2. 手动开启矩阵变量功能

    • 禁用矩阵变量的原理

      SpringBoot默认是禁用矩阵变量的,需要定制化SpringMVC中的组件手动开启矩阵变量功能,相应的核心属性是WebMvcAutoConfiguration中的configurePathMatch配置路径映射组件的UrlPathHelper对象中的removeSemicolonContent属性;SpringBoot对路径的处理依靠UrlPathHelper对路径进行解析,当removeSemicolonContent属性为true时表示移除路径中分号后面的所有内容,矩阵变量会被自动忽略,因此使用矩阵变量需要自定义removeSemicolonContent属性为false的组件configurePathMatch

    • 自定义configurePathMatch组件

      SpringBoot为组件自定义提供了三种方案,详情见 [4.1.1、 SpringMVC自动配置概览],这里自定义configurePathMatch组件有两种方式

      • 方式一:在自定义配置类WebConfig中用@Bean注解给容器配置一个WebMvcConfigurer组件

      • 方式二:让自定义配置类WebConfig实现WebMvcConfigurer接口,由于Jdk8有该接口的默认实现,可以只实现该接口的configurePathMatch方法,将自定义的urlPathHelper通过该方法传参configurer的setUrlpathHelper(urlPathHelper)方法配置到容器中即可

  3. @MatrixVariable注解的用法

    • 在形参前面使用@MatrixVariable("low")注解可以获取矩阵变量路径中变量名为low的字面值并自动赋值给形参,形参类型不限,含多个同名参数需要使用List集合类型的形参进行接收

    • 配置@MatrixVariable注解的pathVar属性可以获取不同路径变量的同名参数

       

@ModelAttribute

参数解析器ServletModelAttributeMethodArgumentResolver是专门检查是否有@ModelAttribute注解的,不能处理复杂参数Model类型

 

常用Servlet API

在控制方法形参列表指定特定参数类型可以自动获取常用的原生Servlet相关对象作为参数,可使用原生Servlet的功能

  1. 可以使用的Servlet API:

    • WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

    • 上述部分API的参数解析器为ServletRequestMethodArgumentResolver,在supportsParameter方法中规定了具体的相关参数类型,原生request和response都可以通过传递过来封装了原生请求和响应对象的参数webRequest获取,具体原理见源码

  2. 以ServletRequest为例分析Servlet API的底层原理

    • 只讲getMethodArgumentValues方法的两个重点判断参数解析器和解析参数值,其他的执行流程见重点3

    【控制器方法准备】

    【步骤1:判断和寻找适合HttpServletRequest类型参数的参数解析器】

    【当循环到resolver为ServletRequestMethodArgumentResolver时的supportsParameter方法解析】

    【步骤2:使用解析器将原生的HttpServletRequest传递给形参】

     

常用复杂参数

在形参列表指定特定参数类型可以自动获取一些复杂参数,如Model可用于向请求域共享数据,doDispatch方法源码解析的重点4中介绍了Map和Model数据从封装到ModelAndView到最后放入请求域中的整个流程

  1. 常用复杂参数清单

    • Map

    • Model

    • Errors/BindingResult

    • RedirectAttributes

    • ServletResponse

    • SessionStatus

    • UriComponentsBuilder

    • ServletUriComponentsBuilder

  2. 测试map和model对应的参数解析器

    • 原理:还是原来的,在supportsParameter方法的调用方法getArgumentResolver中对26种参数解析器进行遍历,测试请求参数类型按顺序依次为Map、Model、HttpServletRequest、HttpServletResponse ,生效的参数解析器依次为MapMethodProcessor、ModelMethodProcessor、

    • 特别注意:经过测试和源码分析,形参同时有参数类型为Map以及参数类型为Model的情况下,实参Object临时数组中存放的Map和Model的引用地址都指向同一个BindingAwareModelMap对象

      【BindingAwareModelMap的继承结构图】

    • 当参数类型为Map且参数没有任何注解时的底层原理

      • 核心1:参数解析器MapMethodProcessor的supportsParameter方法检查参数类型是否Map且参数上没有注解

      • 核心2:参数解析器MapMethodProcessor的resolveArgument方法直接调用mavContainer. getModel()方法从mavContainer对象【ModelAndViewContainer】中获取空的defaultModel属性【BindingAwareModelMap】并将其作为实参传入类型为Map的形参,defaultModel是一个空Map ,同时也是一个Model,这个defaultModel可以直接从mavContainer对象中随时拿

      【MapMethodProcessor的相关方法】

      【ModelAndViewContainer中的defaultModel属性】

    • 当参数类型为Model

      • 核心1:匹配参数解析器的方法没说,以后研究一下

      • 核心2:参数解析器ModelMethodProcessor的resolveArgument方法还是直接调用mavContainer. getModel()方法,仍然返回ModelAndViewContainer类型的mavContainer对象中BindingAwareModelMap类型的属性defaultModel,仍然是一个空的map,也是一个model

      【ModelMethodProcessor的相关方法】

  3. Modle和Map类型形参向请求域共享数据的原理

    • 执行完形参数据封装成object数组,立即去执行控制器方法,获得控制器方法的返回值,随后将返回值处理封装到ModelAndView中【该过程给mav构造方法传参defaultModel,执行构造方法时defaultModel中的数据被封装成新的ModelMap存入ModelAndView】返回给doDispatch方法

    • 转头立即去执行所有拦截器的postHandle方法,

    • 随后执行processDispatchResult方法处理和派发结果,调用render方法获取视图名以及最佳匹配视图并

    • 在render方法中调用view.render方法把ModelMap中的数据全部转移到LinkedHashMap类型的mergedModel中,然后调用renderMergedOutputModel方法,传参mergedModel和原生request对象

    • 在renderMergedOutputModel方法中调用exposeModelAsRequestAttributes方法使用Map流式编程遍历key和value并全部放入请求域中

 

自定义对象参数

与属性同名的请求参数会被自动封装在对象中,场景举例:传递参数的名字和pojo类的属性名相同,或者与属性指向的对象的属性名相同(即级联属性),可以使用SpringBoot实现前端提交的数据被自动封装成pojo对象

  1. 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定

【pojo类】

【前端测试页面】

  1. 注意:

    • 涉及级联属性的前端表单页面提交数据的名字也要用pet.name对级联属性进行区分,否则无法为级联属性赋值

    • 且多个相同参数名而属性只能接收一个就只取第一个请求参数的值

【控制器方法代码】

  1. 数据绑定的底层原理

    • POJO类使用的参数解析器是ServletMethodAttributeMethodProcessor【注意有两个参数解析器都叫做ServletMethodAttributeMethodProcessor,第6个和第25个,只有第25个能解析Pojo类自定义参数,这是为什么呢】

      • 这个参数解析器的supportsParameter方法是从父类ModelAttributeMethodProcessor继承来的且没有重写

      • resolveArgument也是从父类ModelAttributeMethodProcessor继承来的且没有重写,打断点会直接进父类执行对应的方法

    • supportsParameter方法

      • 只要有@ModelAttribute注解的参数或者不是必须标注注解且不是简单类型的参数ServletMethodAttributeMethodProcessor参数解析器就可以解析该参数

    • resolveArgument方法

      • 先通过createAttribute方法创建一个对应自定义类型参数的空Pojo实例出来

      • 判断pojo实例没有绑定数据就会通过绑定工厂的binderFactory.createBinder方法创建一个Web数据绑定器WebDataBinder binder对象

    • 太烦了,先记住以后几个重点,自己慢慢理一下

      • WebDataBinder

      • WebDataBinder中有124个converter

      • 可以自定义converter把任意类型转换成我们想要的类型【valueOf方法】

      【源码解析】

      1. supportsParameter方法中调用的相关方法源码

      【ModelAttributeMethodProcessor的supportsParameter方法中的BeanUtils.isSimpleProperty方法】

      【BeanUtils.isSimpleProperty方法中的isSimpleValueType方法】

      1. resolveArgument方法中调用的相关方法源码

        此例中是String到Number类型的转换

      【ModelAttributeMethodProcessor的resolveArgument方法中的bindRequestParameters方法】

      【bindRequestParameters方法中的bind方法】

      MutablePropertyValues mpvs对象中请求参数的存在形式

      • 这里age是数组是因为俺在测试级联属性不加前缀导致的age参数名重复

      MutablePropertyValues

      【bind方法中的doBind方法】

      【doBind方法中的super.doBind方法】

      【super.doBind方法中的applyPropertyValues方法】

      【applyPropertyValues方法中的setPropertyValues方法】

      【setPropertyValues方法中的setPropertyValue方法】

      【setPropertyValue方法中的nestedPa.setPropertyValue方法】

      【nestedPa.setPropertyValue方法中的processLocalProperty方法】

      【processLocalProperty方法中的this.convertForProperty方法解析】

      【this.convertForProperty方法中的convertIfNecessary方法】

      【convertIfNecessary方法中的typeConverterDelegate.convertIfNecessary方法解析】

      【getConversionService方法分支1】


      【convertIfNecessary方法的canConvert方法】

      【canConvert方法中的getConverter方法】

      【getConverter方法中的find方法】

      这个GenericConversionService貌似编写了转换器转换规则的所有东西,设置每一个参数值的时候都会在所有converter中去找那个可以将前端请求参数的数据类型转换到指定的pojo属性的数据类型,请求参数的类型也有非字符串类型,比如文件上传就是以流的形式上传,转换器就会把流转换成文件类型进行操作

      【convert方法分支2】


      【convertIfNecessary方法的conversionService.convert方法】

      【convert方法中的ConversionUtils.invokeConverter方法解析】

      【invokeConverter方法中的converter.convert方法解析】

      【convert方法中的this.converterFactory.getConverter方法】

      最终使用的String转Number用的StringToNumberConverterFactory【String到Number的转换工厂】

      由此启发:我们可以依葫芦画瓢自定义一些类型转换器,T表示要转换的类型,由Converter接口继承来,在实现类中指定T要继承的目标类型,【还有这个用法,T还是妙啊】

      【转换器中convert方法中的NumberUtils.parseNumber方法】

      md封了这么多层最后还不是valueOf,服了

  2. WebDataBinder的配置

    • Web数据绑定器是使用ConfigurableWebBindingInitializer自动向容器中配置的数据绑定器,ConfigurableWebBindingInitializer的调用者之一是WebMvcAutoConfiguration的getConfigurableWebBindingInitializer方法通过ConfigurableWebBindingInitializer.class给容器中配置了一个ConfigurableWebBindingInitializer类型的数据绑定器,ConversionService也是从该容器中拿的;

    • ConfigurableWebBindingInitializer中有一个initBinder方法,该方法给WebDataBinder中设置了各种东西,其中之一就是conversionService类型转换器,ConversionService接口的子接口ConfigurableConversionService接口的实现类GenericConversionService中有非常多的converter转换器,这些Converter就是来进行类型转换的,所有converter的总接口就是Converter<S,T>,这是一个函数式接口:S表示SourceType,T表示TargetType

       

  3. 自定义Converter

    • 对SpringMVC的定制都采用给容器中放一个WebMvcConfigurer组件的方式,该组件中扩展的所有功能都能使用,WebMvcConfigurer是一个接口,里面有一个默认实现的方法叫做addFormatters,意为添加一些格式化器

    • 具体实现自定义Converter代码

      • 步骤一:在配置类中使用@Bean注解添加WebMvcConfigurer组件,感觉这个写法也是匿名内部类

      • 步骤二:在WebMvcConfigurer的匿名内部类中重写addFormatters方法,传参是registry

      • 步骤三:在addFormatters方法中调用registry.addConverter方法添加以匿名内部类的方式创建的自定义converter对象,并重写convert方法,传入的source就是页面提交会赋值给目标类的请求参数,在convert方法中完成对应的参数解析,并给属性值赋值即可

      这样,当SpringMVC拿着"阿猫,3"要去给pet属性赋值时,需要把字符串的"阿猫,3"转换为Pet类型,一查有这个转换器,就用这个转换器的convert方法传入"阿猫,3"并返回Pet类型的对象,此时创建Pet对象和给参数值赋值都是用户自己完成的,不像之前springMVC自己创建空pojo

       

响应数据处理

即控制器方法的返回值在SpringMVC中的处理过程

在这里插入图片描述

返回值处理器原理

ReturnValueHandler,这个在核心对象中已经进行了基本的讲解,这里讲一下返回值处理器的两个方法

返回值处理的核心流程【这个处理过程是在handle方法中执行完执行目标方法获取ModelAndView之前进行的】

  • 遍历返回值处理器,调用所有返回值处理器的selectHandler方法获取合适的返回值处理器

  • 通过获取的返回值处理器调用其handleReturnValue方法处理返回值

    • 在处理响应json的返回值处理器中的handleReturnValue方法中调用writeWithMessageConverters方法通过消息转换器MessageConverter进行处理,实现将返回数据写为json

响应json

jackson.jar+@ResponseBody

这种方式只要引入web场景,给控制器方法添加@ResponseBody注解就会自动给前端响应json数据格式的字符串

  1. 引入starter-web依赖

  2. 给控制器方法标注@ResponseBody注解

    能够处理标注了@ResponseBody注解的返回值处理器是RequestResponseBodyMethodProcessor

  3. 控制器方法返回一个对象会自动返回对应的json格式字符串

  4. 源码分析

    4.4.2中的重点3中的【invokeAndHandle方法中的this.returnValueHandlers.handleReturnValue方法解析】已经对处理返回值的代码进行过简单解析了,这里只讲重点

    标注了@ResponseBody注解的控制器方法会利用RequestResponseBodyMethodProcessor返回值处理器中的消息转换器进行处理,把对象自动转成json格式的Byte数组会使用处理器中的消息转换器MappingJackson2HttpMessageConverter将对象转成json

    invokeHandlerMethod方法中的invokeAndHandle方法解析

    • 重点:调用this.returnValueHandlers.handleReturnValue方法,传参返回值、返回值类型、webRequest、mavContainer;handleReturnValue方法是开始处理控制器方法的返回结果的核心方法

    【invokeAndHandle方法中的handleReturnValue方法】

    • 返回值处理器都继承了HandlerMethodReturnValueHandler接口,重写了其中的用于判断是否支持当前类型返回值returnType的supportsReturnType方法和对返回值进行处理的handleReturnValue方法

    【分支一:selectHandler方法分支】


    【handleReturnValue方法中的selectHandler方法】

    【selectHandler方法中的supportsReturnType(returnType)方法】

    因为这里遍历会调用所有返回值处理器的supportsReturnType方法,这里只以第一个ModelAndViewMethodReturnValueHandler为例

    【分支二:handleReturnValue方法分支】


    这里返回person类型的字符串添加了@ResponseBody注解,对应的返回值处理器是RequestResponseBodyMethodProcessor,分析handleReturnValue方法选取相应返回值处理器的

    【handleReturnValue方法中的handler.handleReturnValue方法】

    【handler.handleReturnValue方法中的writeWithMessageConverters方法】

    • 媒体类型涉及内容协商

      • 在请求头的Accept相关信息中声明了浏览器能接受的响应内容类型

      • 服务器会根据自己自身的能力,决定服务器能生产出什么样内容类型的数据

      • SpringMVC会挨个遍历所有容器底层的HttpMessageConverter【HttpMessageConverter是所有消息转换器都要实现的接口】查找能处理相应响应内容的消息转换器

        • 响应Json会找到MappingJackson2HttpMessageConverter,该消息转换器可以把对象转成json写出到浏览器,在writeSuffix方法一执行完浏览器就会显示对应的结果,此时还在handle方法中

    【writeWithMessageConverters方法中的write方法】

    【write方法中的writeInternal方法】

    注意,这个writeInternal方法是消息转换器MappingJackson2HttpMessageConverter继承于AbstractGenericHttpMessageConverter的,在这个方法中实现了把对象转成json格式的Byte数组

内容协商原理

根据不同的返回值类型或者控制器方法上标注的注解,SpringMVC底层会选择不同的返回值处理器,返回值处理器会根据转换返回值类型和服务器能提供的内容类型匹配合适的消息转换器,选择消息转换器的一个重点就是内容协商,通过遍历所有的消息转换器,最终找到一个能合适处理对应媒体类型的消息转换器

内容协商的核心是根据客户端的不同,返回不同媒体类型的数据

  1. 操作流程

    • 第一步:引入XML依赖【jackson也支持XML类型的响应内容类型,需要引入对应的依赖,不像默认的json,自动就引入了,这个需要手动引入,不需要管理版本】

    • 第二步:使用postman发送一个Accept为xml的请求

      只需要更改请求头中的Accept字段,告诉服务器服务器该客户端可以接受的类型是application/json、application/xml

    • 第三步:内容协商源码跟踪

      这一部分的流程在响应json就已经跟过了,只是内容协商部分这里进行了细讲

      • 要点一:如果内容协商前拦截器对响应进行了部分处理,可能已经确定了内容响应的内容类型,这时候直接设置内容类型为已经设定的内容类型

      • 要点二:之前没有对响应进行处理就调用getAcceptableMediaTypes方法可以获取客户端请求头的Accept字段 【application/xml】,查询出所有客户端能接收的内容类型

        • 通过contentNegotiationManager内容协商管理器【里面有一个strategies的ArrayList,保存了HeaderContentNegotiationStrategy,表示内容协商管理器默认使用基于请求头的内容协商策略】

        • 下图是内容协商管理器中的所有策略

          • 内容协商策略是一个函数式接口ContentNegotiationStrategy,有非常多的实现类,可以基于请求头,基于路径、扩展变量等等等等,HeaderContentNegotiationStrategy就是其中一个基于请求头的

          • 所以strategy.resolveMediaTypes(request)调用的是HeaderContentNegotiationStrategy基于请求头的resolveMediaTypes方法来确定客户端可以接收的内容类型

        • 使用PostMan可以自定义Accept字段,浏览器无法自定义请求头,除非浏览器发送Ajax时指定请求头的Content-Type属性,针对浏览器无法更改请求头Accept字段,SpringMVC底层也实现了针对浏览器内容协商的快速支持

      • 要点三:遍历循环所有当前系统的MessageConverter,找到支持操作对应对象的消息转换器,把converter支持的媒体类型统计出来【application/json、application/*+json】(MappinJackson2能支持自定义对象的原因是supports方法直接返回true),服务端根据返回值类型和消息转换器统计出能支持的若干种可以转换的类型,这里对应Person有json、xml相关的十种处理能力

        【result中具体保存的媒体类型】?为什么有不同元素相同对象的

        result中保存了当前系统对当次请求支持返回json和xml类型的数据类型的统计结果

      • 要点四:进行内容协商的最佳匹配

        • 先遍历可以接收的媒体类型,嵌套遍历可以产生的媒体类型,将二者都有的媒体类型添加到mediaTypesToUse中,这里面保存了对应的媒体类型和相应权重

        • 然后对mediaTypesToUse简单排序和依次判断,从前到后优先选取第一个有效的媒体类型存入selectedMediaType中,排序会把权重高的放在前面

      • 要点五:选择能处理对应媒体类型的消息转换器

        • 根据selectedMediaType中的媒体类型,再次对消息转换器进行遍历,选出能够写出对应媒体类型的消息转换器,调用该消息转换器的write方法进行转换

        MessageConverter在整个内容协商中用了两次,第一次是看当前系统所有的MessageConverter能够支持某个返回值类型能写出的所有媒体类型;第二次是遍历所有MessageConverter获取能够写出特定媒体类型的转换器;

        这里可以对第一次进行优化,因为消息转换器总是固定的,可以在第一次请求进来的时候获取producibleTypes时将producibleTypes缓存起来,以后相同请求进来没必要再遍历所有消息转换器,直接从缓存中拿producibleTypes

      【分支一:getAcceptableMediaTypes方法分支】

      【writeWithMessageConverters方法中的getAcceptableMediaTypes方法】

      【getAcceptableMediaTypes方法中的resolveMediaTypes方法】

      【resolveMediaTypes方法中的resolveMediaTypes方法】

      【分支二:getProducibleMediaTypes分支】

      【writeWithMessageConverters方法中的getProducibleMediaTypes方法】

      【分支三:write方法分支】

      【writeWithMessageConverters方法中的write方法】

      【write方法中的writeInternal方法】

      【writeInternal方法中的writeValue方法】

基于请求参数的内容协商

开启基于请求参数的内容协商功能

为了方便浏览器内容协商,SpringMVC提供基于请求参数的内容协商功能【不改请求头,实现响应xml和json的随心所欲切换】

  1. 步骤:

    • 第一步:在全局配置文件中配置spring.mvc.contentnegotiation.favor-parameter=true【默认是false】

      表示开启基于参数方式的内容协商,该属性规定可以在请求参数中带名为format的请求参数,通过format指定需要响应的内容类型

    • 第二步:发送请求时请求字段带上format请求参数指定响应内容类型

  2. 源码跟踪

    边缘的不讲了,这里只讲内容协商的getAcceptableMediaType方法确定浏览器可以接受的内容类型

    • 核心就是开启请求参数的内容协商功能后会自动把参数内容协商策略放在默认的请求头内容协商策略前面,优先获取请求参数format的参数值对应的内容类型,如果确定了内容类型就跳出策略遍历;如果获取的内容类型是*/*,就继续遍历下一个内容协商策略

    【writeWithMessageConverters方法中的getAcceptableMediaTypes方法】

    以前contentNegotiationManager中的strategies只有基于请求头的内容协商策略HeaderContentNegotiationStrategy,现在开启了基于请求参数的内容协商功能后,多了一个基于参数的内容协商策略ParameterContentNegotiationStrategy,里面参数的名字就叫做format

    【getAcceptableMediaTypes方法中的resolveMediaTypes方法】

    【resolveMediaTypes方法中的resolveMediaTypes方法】

    注意啊这里第一个是基于参数的resolveMediaTypes方法了,不在是基于请求头的内容协商策略中的方法了

 

 

自定义MessageConverter

使用@ResponseBody注解标注控制器方法就意味着响应数据,会调用返回值处理器RequestResponseBodyMethodProcessor处理返回值,使用消息转换器进行处理,所有MessageConverter一起可以支持各种媒体类型数据的读和写操作,读对应canRead方法和read方法,写对应canWrite和write方法;通过内容协商就能找到最终的MessageConverter,调用消息转换器对应的write方法来实现内容类型的转换和写出

自定义消息转换器场景:

浏览器发请求希望返回xml数据,如果是ajax请求希望返回json数据,如果是其他的app请求,则返回自定义协议数据【以前的解决办法是写三个方法,每种场景发送不同路径的请求】;现在可以直接使用内容协商一个方法解决,分别规定请求发送时的需要的内容类型,服务器通过内容协商找到对应的消息转换器分别处理即可

  1. 需求:

    • 扩展场景

      • 如果是浏览器发请求直接返回xml数据 [application/xml] jacksonXmlConverter

      • 如果是ajax请求,返回json数据 [application/json] jacksonJsonConverter

      • 如果是客户端如硅谷app发请求,返回自定义协议数据 [application/x-guigu] xxxConverter

        • 属性值1;属性值2;...[这种方式只要值,省了很多数据,传输更快]

        即适配三种不同的场景

    • 解决流程:

      • 第一步:添加自定义的MessageConverter进系统底层

      • 第二步:系统底层会自动统计处所有MessageConverter针对返回值类型能操作的内容类型

      • 第三步:服务器根据客户端需要的内容类型以及自己能提供的内容类型进行内容协商,自动调用对应的自定义消息转换器处理返回值

        如需要x-guigu,服务器一看刚好有x-guigu对应的转换器,就自动使用自定义消息转换器来处理相应的返回值

  2. 消息转换器的默认配置

    • 在配置类WebMvcAutoConfiguration中的子配置类WebMvcAutoConfigurationAdapter中配置了一个configureMessageConverters组件,该组件在系统加载时会依靠这个组件来配置默认的所有MessageConverter

      【WebMvcAutoConfiguration中的configureMessageConverters方法中的getConverters()方法】

      【HttpMessageConverters构造方法中的getDefaultConverters()方法】

      【getDefaultConverters()方法中的super.getMessageConverters()方法】

      【getMessageConverters()方法中的addDefaultHttpMessageConverters方法】

      解释了只要导入了jackson处理xml的相关类,xml的MessageConverters就会自动添加到converters集合中

  3. 自定义消息转换器的流程【使用设置Accept为自定义内容类型的方式】

    • 步骤一:WebMvcConfigurer接口中有一个configureMessageConverters方法【配置消息转换器,这个会覆盖掉所有默认的消息转换器】,还有一个extendMessageConverters方法【扩展消息转换器,在默认的基础上额外追加】,所以添加自定义消息转换器只需要添加WebMvcConfigurer组件的匿名内部类并重写该组件中的extendMessageConverters方法

    • 第二步:自己在converter包下写一个自定义的消息转换器GuiGuConverter实现消息转换器的总接口HttpMessageConverter<>,括号中为该消息转换器支持的数据类型,这里只设定为Person

    • 第三步:通过postMan发送Accept为x-guigu的请求

      注意,这里还是使用的请求头内容协商策略,参数内容协商策略执行了但是没有得到结果

      在获取所有服务器支持的内容类型中this.messageConverters多出一个自定义的消息转换器,result中保存的是application/x-guigu,此外加上可以转json和xml的,最终producibleTypes可以产生11种数据,但是浏览器只需要一种application/x-guigu,最佳匹配最终确定application/x-guigu;接着遍历消息转换器,查看哪种消息转换器可以处理,找到调用对应的write方法写出去

  4. 自定义消息转换器的流程【基于请求参数的内容协商方式】

    基于参数的内容协商默认只支持xml和json,需要自己自定义内容协商管理器来支持第三方的内容协商策略,WebMvcConfigurer中的configureContentNegotiation方法传参configurer对象的.strategies方法可以自定义内容协商策略,这个内容协商策略会覆盖掉默认的相同类型的内容协商策略【还会取代内容协商管理器中的所有内容协商策略,如基于请求头的内容协商策略】,所以需要补充json和xml,注意:当前策略没法解析出结果,比如基于请求头内容解析策略但是没有请求头内容解析策略对象就会默认媒体类型为*/*,即服务器认为浏览器什么类型都能接受,会直接匹配服务器能提供的第一个媒体类型进行返回,也就导致Accept随便怎么写都返回application/json类型的数据

    • 自定义基于参数内容协商策略的代码

      【自定义基于参数的内容协商管理器后的内容协商管理器】

      只有一个自定义的策略了,自定义策略支持三种媒体类型,注意还要和自定义的对应消息转换器的媒体类型联动,否则没法获取相应的消息转换器处理对应返回对象

       

视图解析

视图解析

  1. 视图解析

    • 视图解析是服务器处理完请求后跳转某个页面的过程

    • 视图解析的方式包括转发、重定向、自定义视图

    • 一般来说处理完请求可以通过以上三种方式跳转JSP页面,但是由于SpringBoot打的是小胖jar,而JSP不支持在压缩包内编译的方式,所有SpringBoot不支持JSP,想要实现页面渲染需要引入第三方模板引擎技术【JSP也是一种模板引擎】

    • SpringBoot支持的第三方模板引擎

      在Using SpringBoot的Starter中有以下场景是springBoot支持整合的模板引擎

      • spring-boot-starter-freemarker

      • spring-boot-starter-groovy-templates

      • spring-boot-starter-thymeleaf

  2. Thymeleaf简介

    • Thymeleaf is a modern server-side Java template;Thymeleaf是一个服务端的java模板引擎,后台开发人员经常会使用Thymeleaf

    • 优点:

      • 语法简单,贴近JSP的方式

    • 缺点:

      • 性能不是很好,高并发场景以及后台管理系统一般都不使用Thymeleaf采用前后端分离的方式,Thymeleaf一般适用于简单的单体应用

  3. Thymeleaf的使用

    • Thymeleaf的语法可以参考官方文档的第四小节Standard Expression Syntax

    • 使用流程

      • 第一步:引入Thymeleaf的相关依赖spring-boot-starter-thymeleaf

      • 第二步:引入了相关依赖Thymeleaf会使用ThymeleafAutoConfiguration自动配置Thymeleaf

        自动配置的策略:

        • 所有Thymeleaf的配置值都在ThymeleafProperties类中

         

        • 配置好了SpringTemplateEngine【但是在更新的版本没看到SpringTemplateEngine】

        • 配置好了ThymeleafViewResolver

      • 第三步:创建html页面,引入Thymeleaf命名空间,使用Thymeleaf就会自动提示,然后编写前端代码

      • 第四步:编写后端代码

         

Thymeleaf语法

表达式
表达式名字语法用途
变量取值${...}获取请求域、session域、对象的值
选择变量*{...}获取上下文对象值
消息#{...}获取国际化等值
链接@{...}生成链接,使用@{}指定的超链接可以自动拼上上下文路径
片段表达式~{...}相当于JSP的include的作用,引入公共页面片段
字面量
文本操作
数学运算
布尔运算
比较运算
条件运算
特殊操作
设置属性值-th:attr

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

标签迭代

使用th:each属性对list集合进行遍历,和JSP差不多,复习复习JSP

【实际应用】

条件运算
属性优先级
OrderFeatureAttributes
1Fragment inclusionth:insert th:replace
2Fragment iterationth:each
3Conditional evaluationth:if th:unless th:switch th:case
4Local variable definitionth:object th:with
5General attribute modificationth:attr th:attrprepend th:attrappend
6Specific attribute modificationth:value th:href th:src ...
7Text (tag body modification)th:text th:utext
8Fragment specificationth:fragment
9Fragment removalth:remove

官方文档 - 10 Attribute Precedence

 

后台管理系统构建

项目创建
添加登陆页面
登录功能代码

转发存在表单重复提交,一般都使用重定向到目标页面

导入菜单模板页面

视图解析原理

视图解析器与视图原理

处理redirect:XXXX的返回值处理器是ViewNameMethodReturnHandler,返回不是null且为字符串就会用这个返回值处理器进行处理,该返回值处理器会把返回的字符串放入mavContainer的view属性中,之前讲过了

  1. mavContainer中存放的信息

    • ignoreDefaultModelOnRedirect 后面再说

    • view 【控制器方法字符串返回值对应的字符串一字不差】

    • defaultModel 【这个就是mavContainer中最开始的那个Model】

    • redirectModel 【重定向新建的Model?】

  2. SpringMVC会根据mavContainer创建ModelAndView

    • 任何控制器方法都会得到一个ModelAndView,ModelAndView中保存了域数据和视图地址【ModelAndView中的Model都是新建的,在利用mavContainer创建ModelAndView时创建的ModelMap;只是重定向视图还会提前创建一个ModelMap,可暂时理解为转发和重定向创建ModelAndView时传入的model不是同一种,一个传参defaultModel(转发),一个传参ModelMap(重定向)】

  3. doDispatch方法的processDispatchResult方法处理派发结果

    这个方法决定了页面该如何响应

    • 调用render方法对页面进行渲染

      • 调用resolveViewName方法根据控制器方法的String类型返回值得到View对象【View对象中定义了页面的渲染逻辑】

        • resolveViewName方法的逻辑是遍历所有的视图解析器尝试是否有能根据当前返回值得到View对象,最终得到了RedirectView;RedirectView是View的一个实现类,View的实现类非常非常多,实际上View这个系统很复杂,AbstractUrlBasedView的同级View有非常非常多

        • 得到视图对象是为了调用自定义的render方法来进行页面渲染工作

          RedirectView通过重定向到一个页面进行渲染

          • 获取目标url地址

          • 使用原生Servlet的response的sendRedirect方法进行重定向

【render方法中的renderMergedOutputModel方法】

为什么这里render方法还在AbstractView中,renderMergedOutputModel方法就直接跳去RedirectView了,这里的renderMergedOutputModel方法和重点4中不一样,单独说一下,以下就是RedirectView的渲染方法

【分支1:createTargetUrl方法分支】

【renderMergedOutputModel方法中的createTargetUrl方法】

【分支2:sendRedirect方法分支】

【renderMergedOutputModel方法中的sendRedirect方法】

  1. 不同场景下的视图解析

    • 返回值以forward:开始:创建InternalResourceView(forwardUrl)-->render策略是request.getRequestDispatcher(request,diapatcherPath)拿到转发器rd[RequestDispatcher],通过rd的forward(request,response)方法进行转发;本质上就是原生Servlet的转发request.getRequestDispatcher(path).forward(request,response)

    • 返回值以redirect:开始:创建RedirectView(redirectUrl)-->render就是调用原生的重定向response.sendRedirect(encodedURL);

    • 返回值是普通字符串,会创建ThymeleafView

      【ThymeleafView的render方法】

      通过以上内容可知我们可以自定义视图解析器+自定义视图来完成更复杂的功能,比如把页面渲染的数据直接包装成excel表格,在自定义视图时在render方法中创建所有的excel表格,使用response将所有的表格响应出去【这些东西在尚硅谷大厂学院里面讲】

拦截器实现登录检查

所有拦截器都继承了接口HandlerInterceptor,拦截器相关的知识见web开发核心对象

  1. 自定义登录检查拦截器代码

  2. 自定义拦截器添加到组件【注册时通过实现WebMvcConfiguration的addInterceptor方法】

    在该步骤中指定拦截规则,拦截所有也包括静态资源在内,排除拦截的路径包括/和/login以及所有的静态资源访问;如果只是精确拦截则不需要管静态资源的事情

 

 

 

文件上传

文件上传实例

文件上传保存实例

  1. 前端页面文件上传表单准备

    重点是表单的单文件和多文件上传

    多文件上传input标签的type属性为file类型,且需要在input标签中添加multiple字段,没有multiple字段就表示单文件上传

    【前端页面效果】

  2. 控制器方法

    重点是如何接收前端上传的文件以及文件的保存

    • 单个文件接收

      • 使用@RequestPart("headImg")注解可以为MultipartFile类型形参自动封装名为headImg的单个文件为单个对象

    • 多个文件接收

      • 使用@RequestPart("photos")注解可以为MultipartFile[]类型形参自动封装名为photos的多个文件为数组、

    • 文件保存

      • MultipartFile对象的isEmpty方法可以判断文件有内容

      • MultipartFile对象的getOriginalFilename方法可以获取原文件的名字,并不是表单中提交的名字而是文件原始名字

      • MultipartFile对象的getName方法可以获取表单中文件的名字,不是原始文件的名字

      • MultipartFile对象的getInputStream方法可以获取原始输入流可以对流进行操作

      • MultipartFile对象的transferTo方法是对getInputStream方法获取的流对象的保存进行了封装,需要传参目标文件File对象【利用Spring自家的fileCopyUtils文件复制工具类的copy方法,传参操作文件对象,目标文件对象实现文件的复制转移】

      • new File("F:\\cache\\"+originalFilename)

        • 一定要注意,java中new File时文件名不存在没关系,没有会自己新建,但是目录名必须有,目录名不会自己新建,实在没有使用mkdir进行创建,

        • 此外注意同名同内容的文件会直接进行覆盖而不会追加,同名文件不同内容也会直接覆盖掉原内容一般适用uuid解决这个问题

    【文件上传解析器】

    这个也是Springboot自动配置的

    【MultipartProperties封装文件上传属性值的文件】

文件上传原理

  1. 文件上传解析器的自动配置原理

    • 文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties【相应属性配置文件】

    • 自动配置了StandardServletMultipartResolver【文件上传解析器】

      • 这个文件上传解析器只能处理遵守Servlet协议上传过来的文件,如果是自定义以流的形式上传的文件需要自定义文件上传解析器

    • 所有文件上传的有关配置属性全部封装在MultipartProperties中

  2. 文件上传实现源码追踪

    • 第一步:判断是否文件上传请求

      • 请求进入doDispatch方法的checkMultipart(request)方法,使用文件上传解析器的isMultipart方法判断是否文件上传请求,是文件上传请求使用文件上传解析器的resolveMultipart方法对原生请求封装成MultipartHttpServletRequest类型的StandardMultipartHttpServletRequest对象并赋值给processedRequest

        在这一步就将请求所有的文件信息全部封装到一个MultiValueMap<String,MultipartFile>Map集合中了,之后直接通过注解的value属性从Map中拿文件作为参数值,这里雷神没有细讲怎么封装的Map【很诡异,value确实是MultiValueMap,但是实际上value是一个StrandradMultipartFile的ArrayList集合,而且也没有看到MultipartFile的继承结构里面有ArrayList】

      • 如果不是文件上传请求则直接把原生请求赋值给processedRequest

    • 第二步:如果是文件上传请求将multipartRequestParsed属性改为true

    • 第三步:handle方法中对自动接收文件的形参的处理

    【分支一:checkMultipart方法分支】


    【doDispatch方法中的checkMultipart方法】

    【小分支1:isMultipart方法分支】

    【checkMultipart方法中的isMultipart方法】

    【小分支2:resolveMultipart方法分支】

    【checkMultipart方法中的resolveMultipart方法】

    【文件上传请求的参数处理过程】

    文件上传请求的参数解析器是RequestPartMethodArgumentResolver,该参数解析器解析请求中的文件内容并封装成MultipartFile对象或者MultipartFile数组

    【】

    【文件上传参数的类型】

    【RequestPartMethodArgumentReolver参数解析器中的resolveArgument方法】

    【resolveArgument方法的resolveMultipartArgument方法】

异常处理机制

SpringBoot异常处理机制参考官方文档Spring Boot Features-->developing-web-applications-->Error Handling

异常机制总览

  1. 默认情况下,SpringBoot会启动一个/error的映射来处理所有的错误,发生错误会自动转发到/error,

    • 如果是机器客户端,会产生一个JSON格式的响应数据【包含错误的时间戳、HTTP状态错误码和错误原因信息、异常信息、那个路径发生了错误】

      【Postman响应的错误信息】

    • 如果是浏览器客户端,会产生一个白页,白页渲染为一个HTML页面,展示相同的错误信息

      【浏览器白页效果】

  2. 自定义错误页面

    • 可以在静态资源目录如static目录下,以及templates目录下设置error/目录,该目录下的HTTP状态码对应的4xx.html,5xx.html会在发生对应错误时会被自动调用【比较常用就是404和服务器内部异常500】

      【实际设置实例】

    • 一般后台管理系统自己就写好了一些错误页面

      【模板404页面】

      【模板500页面】

    • 自定义错误信息展示

      【前端页面代码】

      【实际效果展示】

异常自动配置原理

  1. 异常处理自动配置原理

    • springframework-boot-autoconfigure-web下的servlet下的error包下专门是针对错误处理的类

      【web错误相关的错误自动配置类】

    • ErrorMvcAutoConfiguration【自动配置异常处理规则】

      【ErrorMvcAutoConfiguration源码】

自动配置的容器组件

根据自动配置原理可以得出不同错误页需求下对应自定义的组件

  • 扩展错误信息-->自定义DefaultErrorAttribute

  • 不使用默认白页使用自定义白页-->自定义BasicErrorController

  • 不想把错误页放在error文件夹下-->自定义BeanNameViewResolver

DefaultErrorAttribute
BasicErrorController
StaticView
BeanNameViewResolver
DefaultErrorViewResolver

异常处理流程

注意:如果控制器方法中的参数无法通过请求参数赋值会报400错误,会提示需要的参数不存在

  1. 完整流程

    • 第一步:执行目标方法handle

      • 目标方法执行期间有任何异常都会被catch到并在RequestMappingHandlerAdapter的invokeHandlerMethod方法的finally语句块中使用webRequest.requestComplete方法将AbstractRequestAttributes属性设置为false,标志当前请求结束,

      • 并且使用dispatchException进行封装所有的异常

    • 第二步:执行视图解析流程【页面渲染】processDispatchResult

      • 在该方法中调用processHandlerException处理控制器方法执行过程中发生的异常,并返回ModelAndView赋值给原本执行完handle方法返回的mv

        • 遍历所有的handlerExceptionResolver,看谁能处理当前异常,所有的处理器异常解析器都保存在handlerExceptionResolvers这个List集合中

          所有的处理器异常解析器都实现了接口HandleExceptionResolver,这个接口中只有一个resolveException方法,拿到HttpServletRequest、HttpServletResponse对象、控制器方法处理器、以及控制器方法发生的异常对象;自定义处理器异常解析器也要返回ModelAndView,要决定跳转到那个页面,页面中要放哪些数据

          【HandleExceptionResolver接口】

          • 系统默认的处理器异常解析器包含:

            • DefaultErrorAttributes

            • HandlerExceptionResolverComposite【注意这是一个处理器异常解析器的组合,里面还有三个处理器异常解析器】

              • ExceptionHandlerExceptionResolver

              • ResponseStatusExceptionResolver

              • DefaultHandlerExceptionResolver

          • 首先遍历第一个处理器异常解析器DefaultErrorAttributes

            调用该解析器的resolveException方法作用是把异常信息放入请求域,这个解析器不会返回ModelAndView,只会返回null,必然会继续遍历后续处理器异常解析器

          • 再遍历其他三个处理器异常解析器

            结果很尴尬,三个里面有两个都是处理特定注解的异常的,剩下一个没讲干啥的;总之没有一个能处理,直接把异常抛出到doDispatch方法,一抛出去有被catch捕获到

      • 第四步:第二步无法解析的异常继续手动抛出,被捕获后执行triggerAfterCompletion方法

        这个方法就是拦截器的倒序最后一步,对异常处理没啥意义,这个执行完,当前请求就执行完了,也就是当前请求压根没有执行异常的处理逻辑,但是下一次请求进来请求路径直接变成了/error;

        即当前请求发生异常,但是没有人能够处理,那么SpringMVC又会liji 再发一次请求,请求的路径的URI直接变成/error,即没人能够处理的异常会把异常信息放在请求域转发到/error【原因是异常没处理最终会交给tomcat处理,Tomcat底层支持异常请求转发,springboot把转发路径默认设置成了/error】

      • 第五步:没人处理的错误会带着异常或者错误信息转发到/error,会被BasicErrorController即SpringMVC中专门处理/error请求的控制器匹配直接再次通过handle方法去执行对应/error的控制器方法,从请求域中拿到错误信息封装到model中,然后调用resolveErrorView方法遍历错误视图解析器集合errorViewResolvers里面默认的由异常机制自动配置的DefaultErrorViewResolver的resolveErrorView方法把响应状态码作为错误页的地址拼接成error/HTTP状态码.html来获取ModelAndView,即模板引擎最终响应error/5xx.html

      【doDispatch方法中的processDispatchResult方法】

      【processDispatchResult方法中的processHandlerException方法】



      【第三步的所有异常解析器的解析过程】

      【以下均为processHandlerException方法中遍历处理器异常解析器的resolveException方法】

      DefaultErrorAttributes即第一个异常解析器先来处理异常,把异常信息保存到request域中,并且返回null,这个异常信息貌似就是最初的doDispatch方法中那个直接放在请求域中,返回null意味着还会继续遍历处理器异常解析器,这里也说明异常解析器必须解析出结果,不解析出结果不算完事

      【DefaultErrorAttributes中的resolveException方法】

      执行处理器异常解析器的组合的resolveException方法会跳去执行HandlerExceptionResolverComposite中的resolveException方法,在该方法中对三个处理器异常解析器进行处理


      【HandlerExceptionResolverComposite中的resolveException方法】


      【ExceptionHandlerExceptionResolver中的resolveException方法】

      只要控制器方法上没有标注ExceptionHandler注解就会返回null,即ExceptionHandlerExceptionResolver异常解析器只能解析控制器方法上标注了ExceptionHandler注解的异常

      【resolveException方法中的doResolveException方法】

      【doResolveException方法的doResolveHandlerMethodException方法】


      【ResponseStatusExceptionResolver的resolveException方法,直接继承的父类的】

      这个异常解析器的作用是如果控制器方法上标注了@ResponseStatus注解,出现错误以后直接给一个响应状态码,这个也不能解析,但是具体原理没讲,包括能解析怎么创建ModelAndView,这里肯定是没有这个注解直接判断返回null了;对自定义异常标注了@ResponseStatus注解的同时会给对应异常一个状态码

      【resolveException方法中的doResolveException方法】

       


      【DefaultHandlerExceptionResolver的resolveException方法】

      woc只说了这个也不能解析,多一嘴都没提,后来补上了

      • 这个控制器异常解析器是专门来处理SpringMVC自己的底层异常的

      【resolveException方法中的doResolveException方法】

      【doResolveException方法中的handleMissingServletRequestParameter方法】

      【tomcat的丑陋错误页】



      好家伙,除了第一个处理器异常解析器把异常放到请求域中,没有一个解析器能解析该异常

      【第四步】

      【doDispatch方法中的triggerAfterCompletion方法】

      这特么就是拦截器最后一步所有已经执行了的拦截器执行AfterCompletion方法,对异常处理没啥意义


      【第五步】

      【BasicErrorController中的errorHtml方法中的resolveErrorView方法】

      【errorViewResolvers中的错误视图解析器】

      DefaultErrorViewResolver这玩意就是自动配置类给底层配置的

定制错误处理逻辑

  1. 自定义错误页

    • error/404.html、error/5xx.html、有精确错误状态码页面就精确匹配,没有就找4xx.html,如果都没有就触发白页

  2. @ControllerAdvice+@ExceptionHandler处理全局异常

    【异常处理器的代码实例】

  3. @ResponseStatus+自定义异常

    @ResponseStatus标注在异常类上,可以用value属性执行异常对应状态码,用reason属性指定异常原因

    底层发生当次异常请求后在遍历控制器异常解析器的时候会直接调用ResponseStatusExceptionResolver 的resolveException方法,把responseStatus注解的信息解析出来直接调用response.sendError(statusCode,resolvedReason)通知tomcat拿着错误状态码和错误信息直接发送/error请求,spring接收/error请求直接像无法处理的请求再次发送/error一样进行处理最终匹配到4xx,5xx,没有就匹配白页,response.sendError方法返回一个view和model均为空的ModelAndView,结束对处理器异常解析器的循环结束当前请求

    【自定义@ResponseStatus标注异常实例】

  4. Spring底层的异常,如参数类型转换异常

    • 控制器方法需要的形参请求参数不存在框架会自动抛异常,然后进入异常处理流程,遍历所有处理器异常解析器,第一个DefaultErrorAttributes把异常放在请求域继续执行,接下来遍历其他三个异常解析器,前两个一个是处理加了ExceptionHandler注解的,一个是处理异常加了ResponseStatus注解的,两个都不行

    • 来到第三个异常解析器DefaultHandlerExceptionResolver,这个异常就是专门处理SpringMVC框架底层的异常,这里以MissingServletRequestParameterException异常讲解SpringMVC底层对框架自身异常的处理逻辑

    • 实际上就是再调用response.sendError方法让tomcat发送/error请求被BasicErrorController匹配到然后调用DefaultErrorViewResolver来处理解析错误视图,解析不了响应SpringMVC的白页,没有SpringMVC会响应tomcat自己的丑陋白页

  5. 所有的异常解析器都实现了HandlerExceptionResolver

    【接口HandlerExceptionResolver】

    我们可以通过实现这个接口自定义控制器异常解析器,自定义异常解析器实现该接口然后使用@Component注解将其放入IoC容器中

    • 要点1:自定义异常解析器会直接放在处理器异常解析器组合后面,即处理器异常解析器集合由原来的两个变成三个,由于自定义异常解析器被放在了最后,很多异常压根轮不到自定义异常解析器进行处理,此时给自定义异常解析器使用order注解通过设定value属性值为Ordered接口的HIGHEST_PRECEDENCE属性可以设定自定义异常解析器的优先级为最高优先级(数字越小优先级越高)

      【修改了优先级后的处理器异常解析器集合】

    • 自定义处理器异常解析器可以自定义返回ModelAndView,设置跳转视图和请求域数据,不设置也要创建ModelAndView,目的是为了让其他处理器异常解析器不再进行遍历,注意如果自定义异常解析器不进行筛选拦截所有异常,SpringMVC所带的所有异常处理机制都会失效

    • 所以只要把自定义处理器异常解析器的优先级调高,就会作为默认的全局异常处理规则,很灵活,可以拦截所有异常按自己的逻辑进行处理,也可以拦部分异常,放行其他异常给Spring来处理

    【处理器异常解析器集合】

    【自定义处理器异常解析器实例】

  6. ErrorViewResolver

    • 这个一般不会自定义,因为Spring底层的异常处理逻辑大都是让tomcat自己发一个/error请求,该请求就会被BasicErrorController匹配调用这个错误页视图解析器解析状态码跳转对应的错误页【两种方式都会让tomcat自己发送/error请求:一种自己直接response.sendError;第二种没有任何人能处理的异常即所有异常解析器都返回null】

     

Web原生组件注入

Servlet、Filter、Listener

具体内容参考官方文档spring-boot-features7.4,使用@WebServlet、@WebFilter、@WebListener注解标注的类可以使用@ServletComponentScan注解自动注册【前面三个注解都是在Servlet规范中的】

两个注解组合注入

@ServletComponentScan+原生注解【@WebServlet | @WebFilter | @WebListener】的方式

  1. 使用原生继承了HttpServlet的Servlet

    作用是移植工程追求迅速上线可以直接把原生servlet全copy过来先不改代码扫一下就能用

    【原生Servlet实例】

注意1:此时是不能直接访问/my请求的,会报错404;需要在主配置类上添加@ServletComponentScan注解指定basePackages属性具体要扫描的包和子包,自动对目标包下写好的servlet进行扫描【只有标注了对应原生注解且实现了对应接口的类才能被扫描】

注意2:这种原生Servlet的方式会直接响应,不会被Spring的拦截器进行拦截

  1. 使用原生实现了Filter接口的Filter

    【原生Filter实例】

  2. 使用原生实现了监听器接口的Listener

    【原生Listener实例】

     

使用RegistrationBean注入

ServletRegistrationBean、FilterRegistrayionBean、ServletListenerRegistrationBean

以ServletRegistrationBean为例,这种用法是直接在配置类中向容器注册一个ServletRegistrationBean组件,而没有添加原生@WebServlet注解的Servlet可以直接创建一个servlet对象,直接通过构造方法向ServletRegistrationBean传入对应servlet和映射路径,映射路径是可变数量字符串参数

Filter和Listener的注入方式是一样的

【使用RegistrationBean注入Web原生组件的实例】

DispatchServlet注入原理

原生Servlet匹配的映射请求不会被Spring拦截的原理

  1. dsadj

    • 目前SpringBoot中有两个Servlet,一个是注入的Web原生servlet即myServlet,另一个是最大的派发处理器DispatcherServlet【前端控制器】

      MyServlet-->处理/my请求

      DispatcherServlet-->处理/请求

    • DispatcherServlet对应的自动配置类DispatcherServletAutoConfiguration

      在自动配置包下的web模块下的servlet包下

      • DispatcherServlet注册步骤

        • 第一步:SpringBoot会给容器中放一个DispatcherServlet,对应的名字为dispatcherServlet

          • DispatcherServlet属性绑定的是WebMvcProperties,对应的配置项前缀是spring.mvc

        • 第二步:给容器配置文件上传解析器

        • 第三步:以RegistrationBean的方式将从容器中拿到dispatcherServlet将DispatcherServlet组件以Web原生Servlet的形式注册到IoC容器中,这里面确定了DispatcherServlet的请求匹配映射规则为"/",通过修改属性spring.mvc.servlet.path可以更改DispatcherServlet的对应匹配路径前缀

       

嵌入式Servlet容器

Web容器自动配置原理

对应官方文档的7.4.3章节

嵌入式Servlet容器的作用即创建SpringBoot应用时,无需外置部署服务器,应用中自带服务器,应用一启动就能够直接使用,默认使用的就是tomcat;原理SpringBoot说是使用了一种特殊的IoC容器ServletWebServerApplicationContext:IoC容器是ApplicationContext类,如果SpringBoot发现当前是一个web应用,IoC容器就会变成ServletWebServerApplicationContext【ServletWeb服务器的IoC容器】,该IoC容器继承了ApplicationContext接口,即IoC容器接口;

ServletWebServerApplicationContext的作用是当SpringBoot启动引导的过程中会搜索一个ServletWebServerFactory【ServletWeb服务器工厂】,这个工厂的作用就是生产ServletWeb服务器

SpringBoot底层一般有很多的WebServer工厂,TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory

【SpringBoot底层支持的Web服务器】

  • 注意UndertowWebServer不支持JSP

  1. 原理

    • 关于对Web服务器的自动配置

      • 在自动配置包下的web包下的servlet下的有一个ServletWebFactoryAutoConfiguration即Web服务器自动配置类

      • ServletWebFactoryAutoConfiguration通过Import注解导入ServletWebServerFactoryConfiguration配置类,在这个配置类中根据系统中到底导入哪个Web服务器的包动态判断需要配置哪种Web服务器工厂【web-starter场景默认导入的是tomcat的包,所以系统底层默认配置的是Tomcat的web服务器工厂TomcatServletWebServerFactory】

      • 最后由TomcatServletWebServerFactory创建Tomcat服务器TomcatWebServer并启动,由于TomcatWebServer的构造器会执行初始化方法initialize....,在执行该方法过程中直接调用TomcatWebServer的start方法,执行完该方法tomcat就启动了,即创建服务器对象的时候tomcat服务器也一并启动了

      • 所谓的内嵌服务器就是Spring来调用tomcat启动服务器的代码而已

      【ServletWebFactoryAutoConfiguration】

      【ServletWebServerFactoryConfiguration】

      【ServletWebServerApplicationContext】

      ServletWeb服务器的IoC容器

      【createWebServer方法中的factory.getWebServer方法】

切换其他Web服务器

在官方文档Using SpringBoot中的1.5starters中有starter-undertow、starter-tomcat、starter-jetty

服务器是由web场景starter-web导入的,默认就是导入的starter-tomcat,此时直接在pom.xml的starter-web中用exclusions标签排除starter-tomcat依赖,然后单独引入目标服务器依赖

  1. 引入其他web服务器pom.xml设置

自定义服务器配置

更改服务器的一些默认配置

定制化web服务器,官方文档spring-boot-features7.4.4

第一种定制方式是修改配置文件

  • 所有关于服务器的配置属性前缀都是server,原因是服务器自动配置类ServletWebServerFactoryAutoConfiguration绑定的文件是Serverproperties,服务器的取值来源都来自Serverproperties,web服务器工厂创建web服务器时会根据工厂定制化器中的serverproperties来确定服务器的属性值;而Serverproperties绑定的配置属性都以server为前缀,Serverproperties类中的属性包括端口号port、地址address【这是什么地址?】、错误页error、安全连接相关信息ssl、Servlet有关的同样信息servlet

    • server.undertow.accesslog.dir=/tmp表示指定访问undertow日志的临时目录

    • server.servlet.session.timeout=60m表示设置session的超时时间为60分钟

第二种方式是直接自定义ServletWebServerFactory并将该组件添加到容器中,推荐自定义ConfigurableServletWebServerFactory,这两都是接口,后者继承了前者

  • 创建以后SpringBoot底层会自动调用该工厂根据相应属性创建web服务器

    【自定义ConfigurableServletWebServerFactory示例】

第三种是自定义WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

  • 通过自定义的工厂化定制器将配置文件的值与ServletWebServerFactory进行绑定

    Spring底层经常出现这种定制化器,这更像Spring的一种设计规则,只要给底层配置了自定义定制化器,就可以改变某些东西的默认行为

    【原始的工厂定制化器】

    【定制工厂定制化器实例】

 

定制化原理

  1. 自动配置原理套路分析

    • 引入starter-XXX场景-->xxxxAutoConfiguration-->导入xxx组件-->绑定xxxProperties-->绑定配置文件项

  2. 常见定制化方式

    • 第一种:编写自定义配置类xxxConfiguation,使用@Bean注解或者@Component注解替换或增加容器中的默认组件,如视图解析器、参数解析器,处理器异常解析器等

    • 第二种:修改配置文件中的配置属性,这种是比较常用的

    • 第三种:自定义xxxxCustomizer即xxxx定制化器,相当于选择性修改工厂创建对象时的部分默认值

    • 第四种:编写一个配置类实现WebMvcConfigurer接口,重写接口中的各种方法来实现定制

    • 第五种:如果想修改SpringMVC非常底层的组件,如RequestMappingHandlerMapping、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver可以给容器中添加一个WebMvcRegistrations组件

      【添加WebMvcRegistrations组件示例】

    • 第六种:如果用户想完全控制SpringMVC,可以添加一个用@Configuration注解标注的配置类,该配置类由@EnableWebMvc注解标注

      【@EnableWebMvc注解实例】

      • 全面接管SpringMVC的原理

        • SpringMVC的所有自动配置功能都在WebMvcAutoConfiguration中,这个类中有对静态资源、欢迎页、视图解析器等等一堆配置

        • 一旦使用@enableWebMvc注解,会用@Import注解导入DelegatingWebMvcConfiguration

        • DelegatingWebMvcConfiguration的作用

          • 作用1:把系统中的所有WebMvcConfigurer拿到,把这些WebMvcConfigurer的所有功能定制合起来一起生效

          • 作用2:这个类继承了WebMvcConfigurationSupport,根据父类中的行为会自动配置了一些非常底层的组件,比如HandlerMapping、内容协商管理器;这些组件依赖的组件都是从容器中获取

            这个类只保证SpringMVC最基本的使用,只有核心组件,如RequestMappingHandlerMapping、Adapter等

        • 核心:WebMvcAutoConfiguration中的配置能生效的必要条件是

          • 条件配置@ConditionalOnMissingBean(WebMvcConfigurationSupporties.class),即没有WebMvcConfigurationSupporties才生效,但是@enableWebMvc注解导入了DelegatingWebMvcConfiguration就相当于有了其父类WebMvcConfigurationSupport,即@enableWebMvc注解会导致SpringMVC配置类WebMvcAutoConfiguration没生效

            这时候只能由WebMvcAutoConfiguration配置的支持Rest风格的过滤器、各种消息转换器等等,都需要自己来进行配置

          【DelegatingWebMvcConfiguration】

           

数据访问

导入JDBC场景

  1. SQL部分

    • 导入数据开发场景-->引入自动配置类-->导入数据源相关组件-->数据源配置项和属性配置文件绑定

      在Using SpringBoot中的starter中有很多以data开始的都是整个数据库访问相关的场景,如jdbc、jpa、redis

    • 用户在上述流程中的角色是导入相关场景,配置属性配置文件

  2. 数据源的自动配置

    • 导入JDBC场景

      【场景导入的依赖】

      这里面缺少JDBC驱动,官方因为不知道用户使用的是那个版本和哪种数据库,所以数据库驱动由用户自己引入,但是SpringBoot对mysql驱动由默认版本仲裁,一般都自己指定自己的版本

      【修改版本的两种方式】

      【 方式一】

      【方式二】

      【当前本机SpringBoot的mysql驱动默认版本】

    • 分析自动配置原理

      数据相关的jdbc自动配置在AutoConfigure中的data包下的jdbc包下,这里面是用来找JDBC的接口类,还有一个jdbc大包,这个包下是对整个数据库相关的配置,该包下的有关配置类见5.1.2

自动配置的类

DataSourceAutoConfiguration

数据源的自动配置类

  • 全局配置文件修改数据源相关配置属性的前缀:spring.datasource

  • 数据库连接池的配置,容器中没有DataSource才自动配置

  • 底层配置好的连接池是:HikariDataSource,这个DataSourceConfiguration.Hikari.class类中已经默认自动配置了spring.datasource.type=com.zaxxer.hikari.HikariDataSource

    关于数据源自动配置的原理讲的不清楚,以后再自己研究,先记结论

【DataSourceTransactionManagerAutoConfiguration】

事务管理器的自动配置类

【JdbcTemplateAutoConfiguration】

JdbcTemplate的自动配置类,JdbcTemplate可以用来对数据库进行CRUD的小组件,这里面跟JDBC相关的配置都在JdbcProperties中,前缀是spring.jdbc

给容器中导入组件JdbcTemplate,通过修改前缀是spring.jdbc的配置属性可以修改jdbcTemplate

只有当容器中没有数据源DataSource这个类才会自动配置数据源

【JndiDataSourceAutoConfiguration】

Jndi的自动配置类

【XADataSourceAutoConfiguration】

分布式事务相关的配置

数据源的配置属性

数据源是用来执行数据库CRUD操作的,数据源就是提供连接对象的

  1. 【配置属性实例】

配置了数据库相关场景,但是没有配置数据源配置属性SpringBoot项目启动会报错,成功的标志就是应用正常启动且没有报错

  1. JdbcTemplate配置实例

  2. 配置德鲁伊druid数据源

    HikariDataSource数据源是目前世面上性能最好的数据源产品,实际开发中也比较喜欢使用阿里的druid德鲁伊数据源,该数据源有针对数据源的完善解决方案,包括:数据源的全方位监控、防止SQL的注入攻击等等

    SpringBoot整合第三方的技术有两种方式

方式一

方式二

整合MyBatis操作

整合MyBatis的流程

  1. 第一步:引入MyBatis的场景启动器

引入场景启动器在GitHub上点入对应starter下的pom.xml【注意版本要选择稳定版,不要选择快照版,从master分支中的标签Tags来切换版本】

【MybatisAutoConfiguration】

【MyBatisProperties】

【SqlSessionTemplate】

  1. 第二步:准备mybatis组件

这里参考Mybatis官方文档

  1. 配置属性配置项

    1. 指定配置文件位置

      在spring全局配置文件中指定Mybatis全局配置文件和Mapper映射文件的类路径下的目录

    2. 指定全局配置文件的信息

      建议不要写mybatis全局配置文件,直接配置在mybatis.configuration属性下即可

注意:驼峰命名规则默认是false,需要在mybatis全局配置文件的Configuration标签中打开

在MybatisProperties中的private Configuration configuration属性中对Mybatis的设置项进行了绑定,同样在全局配置文件中指定对应的属性配置值就相当于在mybatis全局配置文件中进行对应的配置,具体要配的值可以看IDEA的提示,注意一旦使用这种方式就不能使用config-location: classpath:mybatis/mybatis-config.xml来指定全局配置文件,即要么只能在mybatiys全局配置文件中配,要么只能在yml中配置【意味着可以不写mybatis全局配置文件,所有的mybatis的全局配置文件都可以单独放在Configuration配置项中即可】

Mybatis纯注解整合

参考Mybatis的GitHub上的starter的Wiki

除了引入Mybatis的starter,还可以在SpringBoot项目初始化的时候选中Mybatis框架,效果是一样的

纯注解写Mapper【如@select注解】可以不用写Mapper映射文件,但是这种方式只适用于简单SQL;好就好在Mapper映射文件和纯注解可以混用,特别复杂的SQL就可以用Mapper映射文件

用@Options注解可以给SQL标签设置属性值,相当于扩展SQL语句的功能

【纯注解的Mapper接口实例】

【service】

【控制层】

混合模式

简单SQL用注解

复杂SQL还是用SQL映射文件

【Mapper接口】

【SQL映射文件】

【service】

【控制层】

最佳实践

  1. 引入mybatis-starter

  2. 配置application.yaml,指定mapper-location位置

  3. 编写Mapper接口并标注@Mapper注解

  4. 简单方法直接使用注解写SQL

  5. 复杂方法编写mapper.xml进行绑定映射

    每个Mapper上都写@Mapper注解太麻烦,可以直接在主应用类上用@MapperScan注解指定Mapper所在的包,这样每个Mapper接口上就不用标注@Mapper注解了

 

 

附录

  1. IDEA快捷键:

    • ctrl + shift + alt + U:在pom.xml文件中使用以分析依赖树的方式显示项目中依赖之间的关系,也可以在pom.xml中右键Diagrams的Show Dependencies显示分析依赖树。

    • Ctrl + Alt + B:查看类的具体实现代码

    • Ctrl+n双击shift的效果相同

    • Ctrl + H : 以树形方式展现类继承结构图

    • Ctrl + Alt + U : 鼠标放在类上以弹窗的形式用UML类图展现当前类的父类以及实现哪些接口

    • Crtl + Alt + Shift + U : 鼠标放在类上以新页面的形式用UML类图展现当前类的父类以及实现哪些接口

    • shift+insert:可以直接把复制的类加入到包中,感觉作用像粘贴,笑了就是粘贴,只是为了方便左撇子

    • Alt+f7:选中类使用该快捷键或者选中find Usages可以列出一个类的调用者

    • 右键方法-->goto-->implement:可以查看对应哪些子类实现了了该方法,按住ctrl点击方法名可能会跳去接口默认实现的方法,以上方法能跳转去子类实现的对应方法,即debug中点击进入方法的效果,不过需要明晰到底去哪个子类中执行了对应的方法

    • shift+f6:给文件重命名

    • ctrl+n:效果和双击shift是一样的

    • alt+鼠标:与鼠标中键的效果相同,块编辑

  2. springBoot应用run方法会返回IoC容器,IoC容器中包含当前应用的所有组件

  3. 从IoC容器中获取组件对象的方法

    • User user01 = run.getBean("user01", User.class);

      通过id和组件的class对象获取组件对象

    • MyConfig bean = run.getBean(MyConfig.class);

      仅通过组件的class对象获取单个组件对象

    • String[] users = run.getBeanNamesForType(User.class);

      从容器中根据类型获取该类型对应的所有组件名字

    • boolean tom = run.containsBean("tom");

      根据名字(id)判断容器中是否存在对应组件,true表示存在,false表示不存在

  4. debug模式下运行至断点位置选中当前行的某段代码右键Evaluate Expression或者alt+F8可以唤出结算界面获取断点行某个代码片段的运算值

  5. SpringBoot的设计思想:

    • SpringBoot默认在底层配置好所有的组件,但是如果用户配置了组件就以用户的优先,比如CharacterEncodingFilter,实现方式是条件装配没有当前类型的组件就生效创建组件,有就自动配置组件失效

  6. 浏览器快捷键:

    • 浏览器使用ctrl+f5是不走缓存发送请求

  7. UrlpathHelper【URL路径帮助器】常用方法介绍

    • decodeMatrixVariables()

      • 解码路径中的矩阵变量方法

    • decodePathVariables()

      • 解码路径变量

    • decodeRequestString()

      • 解码请求字符串

    • ...

  8. 浏览器相关操作

    • Application中可以查看cookies,自己发给浏览器的cookie浏览器会在原来发回来的cookie后面追加

  9. Map集合的流式编程

    可以直接用变量表示key和value然后直接用

  10. HttpServletResponse接口中有所有的Http响应状态码以及相应的状态信息,从345行开始

  11. debug查看对象属性时Byte数组可以View as String

  12. Arrays.asList(headerValueArray);//将字符串数组转成List集合

  13. 设置服务器的前置路径:

    • 在属性配置文件中设置属性server.servlet.context-path为自定义上下文路径如/world即可【以后所有的请求都以/world开始】

  14. 浏览器中li标签的class="active"属性可以设置选中状态高亮,实现原理是什么,怎么实现公共页面抽取后仍然能实现对应选项高亮?

  15. @Bean注解的destoryMethod属性能指定应用程序关闭前调用指定的自定义shutdown方法来销户该容器组件

  16. 没有使用@ResponseBody但是响应一个对象给客户端会显示500错误,抛出thymeleafTemplateInputException